Introduction

With the advancement of single-cell technologies, we can now profile multiple modalities in the same cell. There are many examples of such approaches:

One of the most popular methods is the Epi Multiome ATAC + Gene Expression from 10x Genomics. You can find the workflow here.

In this tutorial, we will use single-cell RNA-ATAC multiomic data from 18-day-old brain organoids, as described in the study Inferring and perturbing cell fate regulomes in human brain organoids. We will analyze the scMultiome data in R using Seurat and Signac

Optional: You can download the raw sequencing data (FASTQ files) from E-MTAB-12002. For this tutorial, we re-generated transcript count and peak accessibility matrices using cellranger-arc count (v2.0.2) with refdata-cellranger-arc-GRCh38-2020-A-2.0.0.

Note: we typically run cellranger pipelines on HPC clusters.

Part 1 - Setup

Step 0 - Load Required Libraries

Begin by loading the necessary libraries:

library(EnsDb.Hsapiens.v86) # Annotation database 
library(biovizBase)
library(Seurat)
library(Signac)
library(Matrix)
library(dplyr)
library(tidyr)
library(ggplot2)
library(presto)
library(pheatmap)
library(yaml)
library(tibble)
library(clustree)
library(clusterProfiler)
library(org.Hs.eg.db)
library(ggrepel)
library(DESeq2)

Note: Both Seurat and Signac are under active development. To ensure compatibility, verify your package versions:

packageVersion("Seurat")
[1] '5.2.1'

Step 1 - Understand the Experiment Setup

Before we dive into the actual analysis, it is always good to examine the dataset. You can check the detailed sample information here, or from the downloaded files:

# Summarize sample information by genetic modification and modality
sample_info %>%
  mutate(
    Modality = case_when(
      grepl("ATAC", Assay.Name) ~ "ATAC",
      grepl("RNA", Assay.Name) ~ "RNA",
      TRUE ~ "Other"
    )
  ) %>%
  group_by(Characteristics.genetic.modification.) %>%
  summarize(
    ATAC_samples = paste(unique(Assay.Name[Modality == "ATAC"]), collapse = ", "),
    RNA_samples = paste(unique(Assay.Name[Modality == "RNA"]), collapse = ", ")
  ) 

In this dataset, S1 and S2 denote the same biological sample sequenced across two different lanes. There are three paired RNA+ATAC datasets: two CRISPR knockouts and one control. Each pair corresponds to a specific sample ID:

  • A4: Control (WT)
  • B4: CRISPR KO with a 29 and 41 bp deletion in GLI3
  • D3: CRISPR KO with a 4 and 8 bp insertion in GLI3

PS: GLI3 is zinc finger protein which is involved in Sonic hedgehog (Shh) signaling.

Step 2 - Understand the Output

To streamline the analysis, multiple runs (i.e., results from cellranger-arc count) were aggregated using cellranger-arc aggr. The aggregation was guided by a libraries_aggr.csv file:

libraries <- read.csv('data/libraries_aggr.csv')
libraries 

Key points:

  • For RNA data, aggr combines count matrices (not an integration step).
  • For ATAC data, aggr merges all fragments and performs new peak calling, ensuring a unified peak set (check here).

Note: Alternatively, peak calling for each sample can be performed using tools like MACS3 or the Signac::CallPeaks. Subsequently, the union/intersection of peaks across samples can be selected for downstream analysis.

Part 2 - Load the Data

Step 1 - Load the Count Matrix

Similar to the standard pipeline for single-cell RNA-seq (scRNA-seq) data, we can use the output folder filtered_feature_bc_matrix.

count_aggr <- Read10X("data/filtered_feature_bc_matrix/")

Since this dataset includes two modalities (RNA & ATAC), the output will contain two matrices. We can check their dimensions as follows:

dim(count_aggr$`Gene Expression`)
[1] 36601 25327
dim(count_aggr$Peaks)
[1] 301573  25327

Before proceeding, we confirm whether the barcodes (cell identities) in both matrices match:

all(colnames(count_aggr$`Gene Expression`) == colnames(count_aggr$Peaks))
[1] TRUE

If TRUE, the barcodes are perfectly aligned across both modalities. If FALSE, further investigation is needed to identify discrepancies.

Step 2 - Exploring the RNA Assay

The RNA assay generates a GENE × CELL matrix, which already provides useful information:

rownames(count_aggr$`Gene Expression`) %>% head()
[1] "MIR1302-2HG" "FAM138A"     "OR4F5"       "AL627309.1"  "AL627309.3"  "AL627309.2" 
seurat <- CreateSeuratObject(counts = count_aggr$`Gene Expression`,
                                 assay = "RNA")
seurat
An object of class Seurat 
36601 features across 25327 samples within 1 assay 
Active assay: RNA (36601 features, 0 variable features)
 1 layer present: counts

Step 3 - Exploring the ATAC Assay

Unlike RNA data, chromatin accessibility data is stored as a PEAK × CELL matrix:

rownames(count_aggr$Peaks) %>% head()
[1] "chr1:9795-10680"    "chr1:180701-181102" "chr1:181191-181694" "chr1:267570-268463"
[5] "chr1:585750-586644" "chr1:629483-630394"

Each feature (row) represents a genomic region (peak) in the format: chromosome:start-end. And we will notice that the matrix is still very big. To not kill this R session because of memory requirements, we can do some filtering first:

min_cells <- round(0.05 * ncol(count_aggr$Peaks))  # Keep peaks present in at least X% of cells
filtered_peaks <- count_aggr$Peaks[rowSums(count_aggr$Peaks > 0) >= min_cells, ]
min_reads <- 100  # Keep peaks with at least Y reads
filtered_peaks <- filtered_peaks[rowSums(filtered_peaks) >= min_reads, ]
dim(filtered_peaks)  # Check new dimensions
[1] 55566 25327

To better interpret the peaks, we can annotate them with the nearest genes. Many genomic databases and tools provide R interfaces, making R a powerful environment for genomic data analysis. Here we follow the Seurat WNN tutorial:

annotations <- GetGRangesFromEnsDb(ensdb = EnsDb.Hsapiens.v86)
seqlevelsStyle(annotations) <- 'UCSC'
genome(annotations) <- "hg38"
################ NOTE #################
## StringToGRanges keep crushing (?) ###
## So we do it manually ##
########################################

# grange.counts <- StringToGRanges(rownames(filtered_peaks), sep = c(":", "-"))

# rownames(filtered_peaks) contains chromosome coordinates in 'chr:start-end' format
peak_names <- rownames(filtered_peaks)  # Example: c("chr1:1-10", "chr2:12-3121")

# Split based on ":" first (to separate 'chr' from 'start-end')
split_peaks <- strsplit(peak_names, ":")

# Extract chromosome names
chr <- sapply(split_peaks, `[`, 1)

# Further split the second part (start-end) by "-"
ranges <- sapply(split_peaks, `[`, 2)
ranges_split <- strsplit(ranges, "-")

# Extract start and end positions as integers
start <- as.integer(sapply(ranges_split, `[`, 1))
end <- as.integer(sapply(ranges_split, `[`, 2))

# Create GRanges object
grange.counts <- GRanges(seqnames = chr, ranges = IRanges(start = start, end = end))

# We'll only use peaks in standard chromosomes
grange.use <- seqnames(grange.counts) %in% standardChromosomes(grange.counts)
filtered_peaks <- filtered_peaks[as.vector(grange.use), ]

We can check the filtered matrix dimension:

dim(filtered_peaks)
[1] 55548 25327

Important: There are other ways of how we could reduce the number of peaks. For example, as mentioned above, if you are using cellranger-arc count outputs directly, you can manually curate a list of peaks that are present in multiple samples (check here). For large datasets, consider leveraging HPC clusters with higher memory capacities.

frag.file <- "data/atac_fragments.tsv.gz"
# Finally create the ATAC assay
seurat[['ATAC']] <-  CreateChromatinAssay(
   counts = filtered_peaks,
   ranges = grange.counts[grange.use],
   genome = 'hg38',
   fragments = frag.file,
   annotation = annotations
 )

Now we have the seurat object with both RNA and ATAC assays:

seurat
An object of class Seurat 
92149 features across 25327 samples within 2 assays 
Active assay: RNA (36601 features, 0 variable features)
 1 layer present: counts
 1 other assay present: ATAC

Optional: Save the object.

saveRDS(
  object = seurat,
  file = "out/raw_seurat.Rds"
)

Part 3 - Monomodal Data Analysis

Step 1 - Quality Control

This section is largely inspired from this tutorial.

# if you start from the prepared seurat object 
seurat <- readRDS('out/raw_seurat.Rds')

For the RNA assay, what we would check is the same as the typical scRNA-seq (i.e., filter out cells with too few or too many detected genes or transcripts (UMIs)).

seurat <- PercentageFeatureSet(seurat, pattern = "^MT-", col.name = "percent.mt", assay = "RNA")

For the ATAC assay, we would also look at the number of detected peaks or detected fragments, similar to the RNA assay. On top of that, we would also exclude cells with too weak fragment enrichment around the transcriptional start sites (TSS). We can also quantify the approximate ratio of ATAC fragments with a strong nucleosome banding pattern (those with fragment lengths around a single nucleosome) that unlikely represent real accessible genomic regions, in relative to the nucleosome-free fragments, and then discard cells with too high of such ratio.

seurat <- NucleosomeSignal(seurat, assay = "ATAC")

For TSS enrichment, we do some tricks here to reduce the computing time:

# Extract TSS annotation
tss <- GetTSSPositions(Annotation(seurat@assays$ATAC))
seurat <- TSSEnrichment(seurat,tss.positions=sample(tss,1000),assay = "ATAC")

After aggregation, barcodes are typically appended with a suffix indicating their origin (e.g., -1, -2, -3). You can use these suffixes to assign sample identities:

# Extract sample identifiers from cell barcodes
seurat$sample <- sapply(strsplit(colnames(seurat), "-"), `[`, 2)
seurat$sample <- plyr::mapvalues(
  seurat$sample,
  from = c("1", "2", "3"),
  to = c("WT", "KO1", "KO2")
)

We can have a look at the QC results for each sample here:

VlnPlot(seurat,
        features = c("nFeature_RNA",
                     "percent.mt",
                     "nFeature_ATAC",
                     "TSS.enrichment",
                     "nucleosome_signal"),
        ncol = 3, group.by='sample',
        pt.size = 0)

Based on the distributions, we can set the filtering criteria as:

seurat <- subset(seurat,
  subset = nFeature_RNA > 1000 &
    nFeature_RNA < 7500 &
    percent.mt < 40 &
    nFeature_ATAC > 1000 &
    nFeature_ATAC < 25000 &
    TSS.enrichment > 1 &
    nucleosome_signal < 1
)

Step 2 - RNA Assay

The analysis for the scRNA assay is rather standard, including data normalization, highly variable genes identification, data scaling, principal component analysis (PCA), and then the UMAP embedding. Additionally, we use the CCA integration from seurat following this tutorial:

DefaultAssay(seurat) <- "RNA"
# to make it work with seurat v5
seurat[['RNA']] <- split(seurat[["RNA"]], f = seurat$sample)
seurat <- NormalizeData(seurat) %>%
  FindVariableFeatures(nfeatures = 3000) %>%
  ScaleData() %>%
  RunPCA(npcs = 50) 
seurat <- IntegrateLayers(object = seurat, method = CCAIntegration,
                  orig.reduction = "pca", new.reduction = "integrated.cca",
                  verbose = FALSE)
seurat[['RNA']] <- JoinLayers(seurat[['RNA']])
seurat <- RunUMAP(seurat, dims = 1:30, reduction = "integrated.cca",
                reduction.name = "umap_rna", reduction.key = "UMAPINTEGRATEDCCARNA_")

For visualization, we use the marker genes mentioned in this tutorial.

# Visualization
marker_genes <- c("MKI67","NES","DCX","FOXG1","DLX2","EMX1","OTX2","LHX9","TFAP2A")

p1 <- DimPlot(seurat, group.by = "sample", reduction = "umap_rna") & NoAxes()
p2 <- FeaturePlot(seurat,
                  marker_genes,
                  reduction = "umap_rna") & NoAxes() & NoLegend()
p1 | p2

We can already see that the 2 KOs and WT are nicely integrated. From maker genes we can also see the separation of progenitors (e.g. NES) and neurons (e.g. DCX), as well as brain regional identities (e.g. FOXG1). We can also do clustering analysis based on RNAseq data alone:

seurat <- FindNeighbors(seurat, reduction = "integrated.cca", dims = 1:30)

We can use clustree to build a clustering tree, which is useful to visualize the relationships between clustering resolutions.

# Make Plot
clustree(seurat, layout="sugiyama", prefix = "RNA_snn_res.")

NOTE: There are indeed many other criteria and algorithms to evalute the number of clusters. Here to make things simple, we just choose one resolution where the clusters are relatively stable.

p1 <- DimPlot(seurat, group.by = "RNA_snn_res.0.7", 
              reduction = "umap_rna", label = T) & NoAxes() & NoLegend()
p1 | p2

Step 3 - ATAC Assay

ATAC-seq data analysis follows a similar workflow to scRNA-seq, including normalization, feature selection, linear and non-linear dimensional reduction, and data integration. However, due to differences in data properties, the specific algorithms used in each step vary.

This section primarily follows the official tutorial from Signac together with this multiome analysis tutorial.

Step 3.1 - Normalization and Linear Dimensional Reduction

Feature selection:The low dynamic range of scATAC-seq data makes it challenging to perform feature selection based on variablity as in scRNA-seq. Instead, we can choose to use only the top n% of features (peaks) for dimensional reduction, or remove features present in less than n cells (similar to what we did when creating this seurat object).

DefaultAssay(seurat) <- "ATAC"
# select the top 25% most common features for faster runtime
seurat <- FindTopFeatures(seurat, min.cutoff = 'q75') 

Normalization: Signac performs Term Frequency-Inverse Document Frequency (TF-IDF) normalization. This is a two-step normalization procedure:

  • Term frequency (TF) normalizes for sequencing depth by scaling peak accessibility counts within each cell
  • Inverse Document Frequency (IDF) down-weights these frequently accessible regions (i.e, peaks that are open across many cells but might not be biologically informative), ensuring that rare but cell-type-specific peaks get more importance.
seurat <- RunTFIDF(seurat)

Linear dimension reduction: We next run Singular Value Decomposition (SVD) to the TD-IDF matrix, which gives Latent Semantic Indexing (LSI) components. This is very similar to PCA, but it can better handle the sparsity (as we can see ATAC matrix is much bigger than the RNA one). The first singular vector often captures sequencing depth (technical variation) rather than biological variation.

seurat <- RunSVD(seurat, n = 50)
# check what the LSI dimensions explain
p1 <- ElbowPlot(seurat, ndims = 30, reduction="lsi")
p2 <- DepthCor(seurat, n = 30) # correlation to sequencing depth 
p1 | p2

Non-linear dimension reduction: Similar to scRNA-seq data, we can visualize the data using UMAP:

seurat <- RunUMAP(seurat,
                  reduction = "lsi",
                  dims = 2:30,
                  reduction.name = "umap_atac",
                  reduction.key = "UMAPATAC_")
p1 <- DimPlot(seurat,
              group.by = "sample",
              reduction = "umap_atac") & NoAxes()
p2 <- FeaturePlot(seurat,
                  marker_genes,
                  reduction = "umap_atac") & NoAxes() & NoLegend()
p1 | p2

Here, both panels are shown in the UMAP space derived from ATAC data. On the left, we observe a relatively uniform “blob-like” structure comparing to the UMAP space from RNA assay. However, when RNA expression from the multiome is overlaid on the same ATAC UMAP (right), we can still see a separation between progenitors and neurons (e.g., MKI67, NES, DCX), as well as evidence of brain regionalization (e.g., FOXG1, DLX2, EMX1, OTX2, LHX2, TFAP2A), indicating that RNA remains the dominant source of information for cell identity and state.

(Optional) Step 3.2 Create a gene activity matrix

To estimate gene activity, we assess chromatin accessibility associated with each gene and generate an inferred RNA assay from the scATAC-seq data. This can be done using a wrapper function GeneActivity():

gene.activities <- GeneActivity(seurat)
seurat[['RNA_inferred']] <- CreateAssayObject(gene.activities) %>% NormalizeData()
DefaultAssay(seurat) <- "RNA_inferred"
p3 <- FeaturePlot(seurat,
                  marker_genes,
                  reduction = "umap_atac") & NoAxes() & NoLegend()
p1 | p3

Here, we show gene activity scores inferred from ATAC profiles. While the activity patterns for some genes, such as FOXG1, partially recapitulate the expression patterns observed in the RNA assay (previous figure), the overall separation between cell types is less distinct. This highlights that while gene activity from ATAC can provide useful proxies for transcriptional programs, it lacks the resolution and specificity of direct RNA measurements.

Step 3.3 Data integration

Integration of ATAC dataset is very similar to RNA, but we will use the LSI components. However, currently Seurat v5 is not compatible with ChromatinAssay, so we will use the old(er) approach. This section largely follows the scATAC-seq data integration tutorial.

DefaultAssay(seurat) <- 'ATAC'

integration.anchors <- FindIntegrationAnchors(
  object.list = SplitObject(seurat, "sample"),
  reduction = "rlsi", # we use Reciprocal LSI
  dims = 2:30 # ignore the first LSI components
)
# Integrate LSI embeddings 
integrated <- IntegrateEmbeddings(
  anchorset = integration.anchors,
  reductions = seurat[["lsi"]],
  new.reduction.name = "integrated.lsi",
  dims.to.integrate = 1:30
) 
# Add the dimension reduction to the original seurat
seurat[['integrated.lsi.atac']] <- CreateDimReducObject(
  Embeddings(integrated, "integrated.lsi")[colnames(seurat),], 
  key="INTEGRATEDLSIATAC_", assay="ATAC"
)

seurat <- RunUMAP(seurat,
                  reduction = "integrated.lsi.atac",
                  dims = 2:30,
                  reduction.name = "umap_atac",
                  reduction.key = "UMAPINTEGRATEDLSIATAC_")

Finally, we can visualize our cells in the integrated ATAC space:

DefaultAssay(seurat) <- 'RNA'
p1 <- DimPlot(seurat,
              group.by = "sample",
              reduction = "umap_atac") & NoAxes()
p2 <- FeaturePlot(seurat,
                  marker_genes,
                  reduction = "umap_atac") & NoAxes() & NoLegend()
p1 | p2

Optional: Save the object.

saveRDS(seurat, 'out/unimodal_seurat.Rds')

Part 4 - Multimodal data integration

Step 1: Visualization in Unimodal Embeddings

# if you start from separately integrated data
seurat <- readRDS('out/unimodal_seurat.Rds')

RNA and ATAC assays capture different aspects of cell identity and regulation. As discussed earlier, RNA typically remains the dominant source of information for defining cell identity and state. We can visualize this by comparing the RNA and ATAC embeddings side by side.

DefaultAssay(seurat) <- "RNA"
Reduce("|", lapply(c("umap_rna",
                     "umap_atac"), function(dr){
  p1 <- DimPlot(seurat,
                group.by = "RNA_snn_res.0.7",
                reduction = dr) & NoLegend()
  p2 <- FeaturePlot(seurat,
                    marker_genes,
                    reduction = dr) & NoAxes() & NoLegend()
  p1 / p2
}))

This side-by-side visualization helps illustrate how each modality captures structure in the data. The RNA-based embedding often shows clearer separation of cell types, while the ATAC embedding may capture regulatory variation not visible in RNA alone.

Step 2 - Weighted Nearest Neighbor Analysis

So far, we have treated the two modalities separately, given their distinct data characteristics. A common strategy for integrating ATAC-seq with RNA-seq data is to infer gene activity scores (e.g., Gene.Activity) from chromatin accessibility profiles.

However, since we are working with multiome data—where both ATAC and RNA are measured from the same cell—we can go a step further. Weighted Nearest Neighbor (WNN) analysis provides an unsupervised framework that learns the relative contribution of each modality per cell. This allows us to perform a more integrated analysis that balances both transcriptomic and chromatin information.

This part mainly follows the Weighted Nearest Neighbor Analysis tutorial from Seurat.

DefaultAssay(seurat) <- 'RNA'
seurat <- FindMultiModalNeighbors(seurat, 
                                  reduction.list = list("integrated.cca", "integrated.lsi.atac"), 
                                  dims.list = list(1:50, 2:30))
seurat <- RunUMAP(seurat, nn.name = "weighted.nn", 
                  reduction.name = "wnn.umap", reduction.key = "wnnUMAP_")
# Here we just choose one resolution, but feel free to change this parameter
seurat <- FindClusters(seurat, graph.name = "wsnn", resolution = 0.7, verbose = FALSE)
p1 <- DimPlot(seurat,group.by = "sample",reduction = "wnn.umap") 
p2 <- DimPlot(seurat,group.by = "wsnn_res.0.7",label=T, reduction = "wnn.umap") & NoLegend()
p3 <- FeaturePlot(seurat,
                  marker_genes,
                  reduction = "wnn.umap") & NoAxes() & NoLegend()
p1 / p2 | p3

This visualization highlights how the WNN approach brings together complementary signals from RNA and ATAC to refine cell clustering and interpretation.

Step 3 - Did using both RNA and ATAC improve my clustering compared to using just RNA?

This part we try to answer this philosophical question: Why do we use multiome? Is it just because we can (afford to) do it?

To begin answering this, let’s compare clustering results:

# RNA-only clustering 
p1 <- DimPlot(seurat, group.by = "RNA_snn_res.0.7", reduction = "umap_rna", label = T) + 
  ggtitle("RNA-only Clustering")

# WNN clustering 
p2 <- DimPlot(seurat, group.by = "wsnn_res.0.7", reduction = "wnn.umap", label = T) + 
  ggtitle("WNN Clustering")

p1 & NoLegend()| p2 & NoLegend() # Combine with patchwork

At first glance, the two clustering results look quite similar. That’s not surprising—RNA often carries the strongest signal for cell identity, especially for broad cell types.

However, when using the same clustering resolution, adding the ATAC modality via WNN can refine local structures and help separate subpopulations that RNA alone might miss.

Let’s quantify the similarity between RNA-only and WNN clusters using a contingency table:

table(RNA=seurat$RNA_snn_res.0.7, WNN=seurat$wsnn_res.0.7)  %>%
  heatmap(xlab='WNN clustering', ylab='RNA-only Clustering', Rowv = NA, Colv = NA)

In some cases, WNN might uncover subtle transitions or regulatory shifts that are invisible in transcriptomic space alone.

Step 4 - Cell Type Annotation

In this step, we use the WNN clustering results to annotate cell types based on marker gene expression.

DefaultAssay(seurat) <- "RNA"
DE_wnn <- wilcoxauc(seurat, "wsnn_res.0.7", seurat_assay = "RNA")
top_markers_wnn <- DE_wnn %>%
  filter(abs(logFC) > log(1.2) &
         padj < 0.01 &
         auc > 0.65 &
         pct_in - pct_out > 30 &
         pct_out < 20) %>%
  group_by(group) %>%
  top_n(10, wt = auc)
top_markers_wnn

From here we can already identify some markers of very early development stages (i.e POU5F1,POU3F1 and RFX4).

Reference Markers from Brain Organoid Data

We provide a curated YAML file from a published brain organoid study, which includes useful marker genes for annotation:

yaml_data <- read_yaml('data/marker_genes.yaml')
df <- as.data.frame(do.call(rbind, lapply(yaml_data, as.list)))
DotPlot(seurat, df$marker_genes, group.by = 'wsnn_res.0.7') + RotatedAxis()

To explore more specific subtypes (e.g., neural progenitor cell (NPC) subtypes):

npc_list <- as.data.frame(do.call(rbind, 
                                  lapply(yaml_data$neural_progenitor_cell$subtypes, as.list)))
all_genes <- unlist(npc_list$marker_genes)
dups <- unique(all_genes[duplicated(all_genes)])
npc_markers <- lapply(npc_list$marker_genes, function(genes) {
  setdiff(genes, dups)
})
DotPlot(seurat, npc_markers, group.by = 'wsnn_res.0.7') + RotatedAxis()

Alternative: Use Average Cluster Expression

In case you think DotPlot is very fabricated, you can compute average expression across clusters and visualize known markers in a heatmap:

avg <- AggregateExpression(seurat, assays = 'RNA', group.by='wsnn_res.0.7',
                           return.seurat=TRUE) 
avg <- NormalizeData(avg)
avg <- GetAssayData(object = avg, assay = "RNA") 

marker_df <- read.table('data/markers.txt', sep='\t')

pheatmap(avg[unique(rownames(marker_df)), ], 
         color = colorRampPalette(c("navy","white", "red"))(20),
         breaks = seq(0,2,0.1),
         scale = "column", # scaling can change the visual effects 
         cluster_cols = FALSE, cluster_rows = FALSE,
         annotation_row = marker_df %>% select(level1, level2, level3))

Assigning Cell Type Annotations

Using a combination of known markers and clustering structure, we can now assign broad annotations to WNN clusters:

meta <- seurat@meta.data %>%
  mutate(stage=case_when(
    wsnn_res.0.7 %in% c(0, 14, 3, 9) ~ 'telencephalon',
    wsnn_res.0.7 %in% c(1, 2, 5, 6, 8, 13) ~ 'early',
    wsnn_res.0.7 %in% c(4, 7, 11, 15, 19) ~ 'nontelencephalon',
    wsnn_res.0.7 %in% c(10, 12, 17, 18) ~ 'other',
    wsnn_res.0.7 %in% c(16) ~ 'neuron'
  )) %>%
  mutate(celltype=interaction(stage, wsnn_res.0.7, drop = TRUE))

seurat <- AddMetaData(seurat, meta)
p1 <- DimPlot(seurat,
              group.by = "stage",
              reduction = "wnn.umap", label = T) & NoAxes() & NoLegend()
p2 <- FeaturePlot(seurat,
                  marker_genes,
                  reduction = "wnn.umap") & NoAxes() & NoLegend()
p1 | p2

We can see that some cells appear ambiguous and are blended into the early developmental stage. If needed, we could further refine this annotation by running FindSubCluster() to split specific clusters. But for now, we’ll keep it simple and proceed with this level of resolution.

Optional: Save the object:

saveRDS(seurat, 'out/bimodal_seurat.Rds')

Part 5 - Downstream Analysis

Step 1 - Differentially Expressed Genes

# If you start from part 5 directly 
seurat <- readRDS('out/bimodal_seurat.Rds')

In the context of a CRISPR knockout (KO) experiment, our main goal is to understand how gene expression changes in KO versus control (WT) cells. For simplicity, we’ll focus on telencephalon progenitors.

Start by checking how the samples are distributed across cell stages:

# Have a look
table(seurat$sample, seurat$stage)

We’ll subset only the telencephalon cells:

seurat_sub <- subset(seurat, stage == 'telencephalon')

To keep things simple, we combine KO1 and KO2 into a single group and compare them against WT:

seurat_sub$group <- ifelse(seurat_sub$sample %in% c("KO1", "KO2"), "KO", "WT")
res_ko_vs_wt <- wilcoxauc(seurat_sub, 'group', seurat_assay = 'RNA')
res_ko_vs_wt_only <- res_ko_vs_wt[res_ko_vs_wt$group == "KO", ]
res_ko_vs_wt_only %>%
  filter(abs(logFC) > log(1.2) &
         padj < 0.05) %>%
  group_by(group) %>%
  top_n(10, wt = auc)

NOTE: If you want to compare KO1 vs WT and KO2 vs WT individually, subset the data before running wilcoxauc() since it compares each group to all others. Alternatively, you can use FindMarkers() with ident.1 and ident.2.

Pseudo-bulk DE Analysis with DESeq2

The best practices for DEG testing in single-cell data are still evolving. One strategy to reduce false positives is to perform pseudo-bulk DE by aggregating counts at the sample level.

pseudo_bulk <- AggregateExpression(seurat_sub, assays = 'RNA', group.by='sample')$RNA
samples <- colnames(pseudo_bulk)
condition <- ifelse(samples %in% c("KO1", "KO2"), "KO", "WT")

sample_info <- data.frame(
  sample = samples,
  condition = condition
)

dds <- DESeqDataSetFromMatrix(countData = pseudo_bulk,
                              colData = sample_info,
                              design = ~ condition)

dds <- DESeq(dds)

# extract the results of KOs vs WT
res <- results(dds, contrast = c("condition", "KO", "WT"))
res_df <- as.data.frame(res)
res_df <- res_df[order(res_df$padj), ]
head(res_df, n=10)

Volcano Plots: Single-cell vs Pseudo-bulk DE

## Plot single cell 
res_ko_vs_wt_only$log2FC = res_ko_vs_wt_only$logFC/log(2)
res_ko_vs_wt_only$significance <- ifelse(res_ko_vs_wt_only$padj < 0.05 & abs(res_ko_vs_wt_only$log2FC) > 0.2, "Significant", "Not significant")

# Select top 10 most significant genes
top_genes <- head(res_ko_vs_wt_only[order(res_ko_vs_wt_only$padj), "feature"], 10)
top_genes <- c(top_genes, 'EMX2', 'GLI3','PAX6','SOX4','SOX11')
res_ko_vs_wt_only$label <- ifelse(res_ko_vs_wt_only$feature %in% top_genes, res_ko_vs_wt_only$feature, NA)

p1 <- ggplot(res_ko_vs_wt_only, aes(x = log2FC, y = -log10(padj), color = significance)) +
  geom_point(alpha = 0.8) +
  geom_text_repel(aes(label = label), max.overlaps = 50) +
  scale_color_manual(values = c("grey", "red")) +
  theme_minimal() +
  labs(title = "Volcano Plot (Single-cell DE)", x = "log2 Fold Change", y = "-log10(adjusted p-value)")

## Plot minibulk
res_df$gene <- rownames(res_df)
res_df$significance <- ifelse(res_df$padj < 0.05 & abs(res_df$log2FoldChange) > 0.2, "Significant", "Not significant")

top_genes <- head(res_df[order(res_df$padj), "gene"], 10)
top_genes <- c(top_genes, 'EMX2', 'GLI3','PAX6','SOX4','SOX11')
res_df$label <- ifelse(res_df$gene %in% top_genes, res_df$gene, NA)

p2 <- ggplot(res_df, aes(x = log2FoldChange, y = -log10(padj), color = significance)) +
  geom_point(alpha = 0.8) +
  geom_text_repel(aes(label = label), max.overlaps = 50) +
  scale_color_manual(values = c("grey", "blue")) +
  theme_minimal() +
  labs(title = "Volcano Plot (Pseudo-bulk DE)", x = "log2 Fold Change", y = "-log10(adjusted p-value)")

p1 & NoLegend()| p2 & NoLegend()

Step 2 - Chromatin Accessibility Profiles Around Markers

So far, we’ve been treating the ATAC assay as just another data layer—but what additional biological insights can it provide?

Chromatin accessibility reflects the openness of genomic regions. The more “open” a region is, the more likely it is accessible to transcription factors and other regulatory proteins. This accessibility often correlates with regulatory activity.

The power of multiome data is that we can measure both chromatin accessibility and gene expression in the same cells, enabling direct insights into gene regulation.

In this step, we’ll: 1. Use RegionStats() to compute base composition information for peaks. 2. Use LinkPeaks() to associate peaks with nearby genes based on correlation between accessibility and expression.

This section is mostly inspired from this section of tutorial.

library(BSgenome.Hsapiens.UCSC.hg38)
seurat_sub <- RegionStats(seurat_sub,assay = 'ATAC',
                      genome = BSgenome.Hsapiens.UCSC.hg38)
seurat_sub <- LinkPeaks(seurat_sub,
                    peak.assay = "ATAC",
                    expression.assay = "RNA",
                    genes.use = top_genes)

You can visualize peak-gene links with CoveragePlot() and explore specific regulatory relationships using browser-style views.

DefaultAssay(seurat_sub) <- "ATAC"
p1 <- CoveragePlot(seurat_sub,
                   region = "HES4",
                   features = "HES4",
                   group.by = "sample",
                   extend.upstream = 5000,
                   extend.downstream = 5000)
p2 <- CoveragePlot(seurat_sub,
                   region = "PAX2",
                   features = "PAX2",
                   group.by = "sample",
                   extend.upstream = 5000,
                   extend.downstream = 5000)
patchwork::wrap_plots(p1, p2, ncol = 1)

Step 3 - TF binding motif enrichment analysis

Following the identification of differentially accessible chromatin regions in Step 2, we now want to ask: Which transcription factors (TFs) might be driving these changes in chromatin accessibility?

This is commonly done by analyzing enrichment of TF binding motifs in the accessible regions.

Step 3.1 - Load Motif Database and Add Motifs to the Seurat Object

We’ll use the JASPAR2020 database of vertebrate motifs and annotate peaks with known TF motifs using AddMotifs().

library(TFBSTools)
library(JASPAR2020)

pfm <- getMatrixSet(
  x = JASPAR2020,
  opts = list(collection = "CORE", tax_group = 'vertebrates', all_versions = FALSE)
)
df_pfm <- data.frame(t(sapply(pfm, function(x)
  c(id=x@ID, name=x@name, symbol=ifelse(!is.null(x@tags$symbol),x@tags$symbol,NA)))))

seurat_sub <- AddMotifs(seurat_sub, genome = BSgenome.Hsapiens.UCSC.hg38, pfm = pfm)

Step 3.2 - Identify Differentially Accessible Peaks (DAPs) in KO vs WT

Similar to gene expression analysis, we now test for differentially accessible peaks using the ATAC assay.

res_ko_vs_wt_atac <- wilcoxauc(seurat_sub, 'group', seurat_assay = 'ATAC')
res_ko_vs_wt_atac <- res_ko_vs_wt_atac[res_ko_vs_wt_atac$group == "KO", ]
top_ko_atac <- 
  res_ko_vs_wt_atac %>%
  filter(padj < 0.01 &
         auc > 0.6) 
top_ko_atac

You can visualize accessibility differences in specific regions using CoveragePlot():

CoveragePlot(seurat_sub,
                   region = "chr11-12214953-12215858",
                   group.by = "sample",
                   extend.upstream = 5000,
                   extend.downstream = 5000)

Step 3.3 Perform Motif Enrichment in Differentially Accessible Peaks

Before running motif enrichment, we match background peaks based on GC content and other region statistics to control for biases.

atac_peaks <- AccessiblePeaks(seurat_sub)
peaks_matched <- MatchRegionStats(
  meta.feature = seurat_sub[['ATAC']]@meta.features[atac_peaks, ], # Get the region stats, like GC% etc
  query.feature = seurat_sub[['ATAC']]@meta.features[top_ko_atac$feature, ])

Now we can perform motif enrichment on the differentially accessible peaks:

motif_enrichment_ko <- FindMotifs(seurat_sub,
                                  features = top_ko_atac$feature,
                                  background = peaks_matched) %>%
  mutate(symbol = setNames(ifelse(is.na(df_pfm$symbol), 
                                  df_pfm$name, df_pfm$symbol), df_pfm$id)[motif]) %>%
  mutate(padj = p.adjust(pvalue, method="BH"))
top_ko_motif <- motif_enrichment_ko %>%
  filter(pvalue < 0.01 & fold.enrichment > 3) ##NOTE: you can choose the filter criteria
top_ko_motif

We can visualize the motifs:

What does this tell us?

The enriched motifs in KO-specific accessible peaks may point to TFs that are more active or upstream regulators of the gene expression programs altered by the knockout.

Part 6 – What’s Next? Additional Downstream Analyses

Now that we’ve covered the core multimodal workflow—visualization, integration, annotation, differential analysis, and motif enrichment. Below are some ideas for additional downstream steps:

  1. Gene Regulatory Network (GRN) Inference
  1. Trajectory and Pseudotime Analysis
  1. Cell-Cell Communication
  1. Chromatin Dynamics
  1. Enhancer-Gene Link Prediction
  1. Functional Enrichment Analysis
  1. Comparative Analysis
LS0tCnRpdGxlOiAiU2luZ2xlIGNlbGwgUk5BK0FUQUMgdHV0b3JpYWwiCmF1dGhvcjogIkhhbnJvbmcgSHUiCmRhdGU6ICIyMDI1LTAyLTI1IgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0gCmtuaXRyOjpvcHRzX2NodW5rJHNldCh3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRSwgcmVzdWx0cyA9ICJoaWRlIikgCmBgYAoKIyBJbnRyb2R1Y3Rpb24gCgpXaXRoIHRoZSBhZHZhbmNlbWVudCBvZiBzaW5nbGUtY2VsbCB0ZWNobm9sb2dpZXMsIHdlIGNhbiBub3cgcHJvZmlsZSBtdWx0aXBsZSBtb2RhbGl0aWVzIGluIHRoZSAqKnNhbWUqKiBjZWxsLiBUaGVyZSBhcmUgbWFueSBleGFtcGxlcyBvZiBzdWNoIGFwcHJvYWNoZXM6CgoqIFtNdWx0aXBsZXhlZCBzcGF0aWFsIG1hcHBpbmcgb2YgY2hyb21hdGluIGZlYXR1cmVzLCB0cmFuc2NyaXB0b21lIGFuZCBwcm90ZWlucyBpbiB0aXNzdWVzXShodHRwczovL3d3dy5uYXR1cmUuY29tL2FydGljbGVzL3M0MTU5Mi0wMjQtMDI1NzYtMCkKKiBbU2luZ2xlLWNlbGwgbXVsdGlwbGV4IGNocm9tYXRpbiBhbmQgUk5BIGludGVyYWN0aW9ucyBpbiBhZ2VpbmcgaHVtYW4gYnJhaW5dKGh0dHBzOi8vd3d3Lm5hdHVyZS5jb20vYXJ0aWNsZXMvczQxNTg2LTAyNC0wNzIzOS13KQoqIFtMaW5raW5nIGdlbm9tZSBzdHJ1Y3R1cmVzIHRvIGZ1bmN0aW9ucyBieSBzaW11bHRhbmVvdXMgc2luZ2xlLWNlbGwgSGktQyBhbmQgUk5BLXNlcV0oaHR0cHM6Ly93d3cuc2NpZW5jZS5vcmcvZG9pLzEwLjExMjYvc2NpZW5jZS5hZGczNzk3KQoqIFtNdWx0aW1vZGFsIGNoYXJ0aW5nIG9mIG1vbGVjdWxhciBhbmQgZnVuY3Rpb25hbCBjZWxsIHN0YXRlcyB2aWEgaW4gc2l0dSBlbGVjdHJvLXNlcXVlbmNpbmddKGh0dHBzOi8vd3d3LnNjaWVuY2VkaXJlY3QuY29tL3NjaWVuY2UvYXJ0aWNsZS9waWkvUzAwOTI4Njc0MjMwMDI4NjY/dmlhJTNEaWh1YikKKiAuLi4KCk9uZSBvZiB0aGUgbW9zdCBwb3B1bGFyIG1ldGhvZHMgaXMgdGhlIFtFcGkgTXVsdGlvbWUgQVRBQyArIEdlbmUgRXhwcmVzc2lvbl0oaHR0cHM6Ly93d3cuMTB4Z2Vub21pY3MuY29tL3Byb2R1Y3RzL2VwaS1tdWx0aW9tZSkgZnJvbSAxMHggR2Vub21pY3MuIFlvdSBjYW4gZmluZCB0aGUgd29ya2Zsb3cgW2hlcmVdKGh0dHBzOi8vd3d3LjEweGdlbm9taWNzLmNvbS9zdXBwb3J0L2VwaS1tdWx0aW9tZSkuCgpJbiB0aGlzIHR1dG9yaWFsLCB3ZSB3aWxsIHVzZSBzaW5nbGUtY2VsbCBSTkEtQVRBQyBtdWx0aW9taWMgZGF0YSBmcm9tIDE4LWRheS1vbGQgYnJhaW4gb3JnYW5vaWRzLCBhcyBkZXNjcmliZWQgaW4gdGhlIHN0dWR5IFtJbmZlcnJpbmcgYW5kIHBlcnR1cmJpbmcgY2VsbCBmYXRlIHJlZ3Vsb21lcyBpbiBodW1hbiBicmFpbiBvcmdhbm9pZHNdKGh0dHBzOi8vd3d3Lm5hdHVyZS5jb20vYXJ0aWNsZXMvczQxNTg2LTAyMi0wNTI3OS04I1NlYzUpLiBXZSB3aWxsIGFuYWx5emUgdGhlIHNjTXVsdGlvbWUgZGF0YSBpbiBgUmAgdXNpbmcgW2BTZXVyYXRgXShodHRwczovL3NhdGlqYWxhYi5vcmcvc2V1cmF0LykgYW5kIFtgU2lnbmFjYF0oaHR0cHM6Ly9zdHVhcnRsYWIub3JnL3NpZ25hYy8pCgoqKk9wdGlvbmFsKio6IFlvdSBjYW4gZG93bmxvYWQgdGhlIHJhdyBzZXF1ZW5jaW5nIGRhdGEgKEZBU1RRIGZpbGVzKSBmcm9tIFtFLU1UQUItMTIwMDJdKGh0dHBzOi8vd3d3LmViaS5hYy51ay9iaW9zdHVkaWVzL2FycmF5ZXhwcmVzcy9zdHVkaWVzL0UtTVRBQi0xMjAwMikuIEZvciB0aGlzIHR1dG9yaWFsLCB3ZSByZS1nZW5lcmF0ZWQgdHJhbnNjcmlwdCBjb3VudCBhbmQgcGVhayBhY2Nlc3NpYmlsaXR5IG1hdHJpY2VzIHVzaW5nIFtgY2VsbHJhbmdlci1hcmMgY291bnRgXShodHRwczovL3d3dy4xMHhnZW5vbWljcy5jb20vc3VwcG9ydC9zb2Z0d2FyZS9jZWxsLXJhbmdlci1hcmMvbGF0ZXN0L2FuYWx5c2lzL3NpbmdsZS1saWJyYXJ5LWFuYWx5c2lzKSAodjIuMC4yKSB3aXRoIHJlZmRhdGEtY2VsbHJhbmdlci1hcmMtR1JDaDM4LTIwMjAtQS0yLjAuMC4gCgoqKk5vdGUqKjogd2UgdHlwaWNhbGx5IHJ1biBjZWxscmFuZ2VyIHBpcGVsaW5lcyBvbiBIUEMgY2x1c3RlcnMuIAoKIyBQYXJ0IDEgLSBTZXR1cAoKIyMgU3RlcCAwIC0gTG9hZCBSZXF1aXJlZCBMaWJyYXJpZXMKCkJlZ2luIGJ5IGxvYWRpbmcgdGhlIG5lY2Vzc2FyeSBsaWJyYXJpZXM6CmBgYHtyLCBtZXNzYWdlPUZBTFNFfQpsaWJyYXJ5KEVuc0RiLkhzYXBpZW5zLnY4NikgIyBBbm5vdGF0aW9uIGRhdGFiYXNlIApsaWJyYXJ5KGJpb3ZpekJhc2UpCmxpYnJhcnkoU2V1cmF0KQpsaWJyYXJ5KFNpZ25hYykKbGlicmFyeShNYXRyaXgpCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkodGlkeXIpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShwcmVzdG8pCmxpYnJhcnkocGhlYXRtYXApCmxpYnJhcnkoeWFtbCkKbGlicmFyeSh0aWJibGUpCmxpYnJhcnkoY2x1c3RyZWUpCmxpYnJhcnkoY2x1c3RlclByb2ZpbGVyKQpsaWJyYXJ5KG9yZy5Icy5lZy5kYikKbGlicmFyeShnZ3JlcGVsKQpsaWJyYXJ5KERFU2VxMikKYGBgCgoqKk5vdGUqKjogQm90aCBgU2V1cmF0YCBhbmQgYFNpZ25hY2AgYXJlIHVuZGVyIGFjdGl2ZSBkZXZlbG9wbWVudC4gVG8gZW5zdXJlIGNvbXBhdGliaWxpdHksIHZlcmlmeSB5b3VyIHBhY2thZ2UgdmVyc2lvbnM6CgpgYGB7ciwgcmVzdWx0cz0ibWFya3VwIn0KcGFja2FnZVZlcnNpb24oIlNldXJhdCIpCmBgYAoKIyMgU3RlcCAxIC0gVW5kZXJzdGFuZCB0aGUgRXhwZXJpbWVudCBTZXR1cAoKQmVmb3JlIHdlIGRpdmUgaW50byB0aGUgYWN0dWFsIGFuYWx5c2lzLCBpdCBpcyBhbHdheXMgZ29vZCB0byBleGFtaW5lIHRoZSBkYXRhc2V0LiBZb3UgY2FuIGNoZWNrIHRoZSBkZXRhaWxlZCBzYW1wbGUgaW5mb3JtYXRpb24gW2hlcmVdKGh0dHBzOi8vd3d3LmViaS5hYy51ay9iaW9zdHVkaWVzL2FycmF5ZXhwcmVzcy9zdHVkaWVzL0UtTVRBQi0xMjAwMi9zZHJmKSwgb3IgZnJvbSB0aGUgZG93bmxvYWRlZCBmaWxlczogCgpgYGB7cn0Kc2FtcGxlX2luZm8gPC0gcmVhZC5jc3YoImRhdGEvRS1NVEFCLTEyMDAyL0UtTVRBQi0xMjAwMi5zZHJmLnR4dCIsIHNlcCA9ICdcdCcpCiMgU3VtbWFyaXplIHNhbXBsZSBpbmZvcm1hdGlvbiBieSBnZW5ldGljIG1vZGlmaWNhdGlvbiBhbmQgbW9kYWxpdHkKc2FtcGxlX2luZm8gJT4lCiAgbXV0YXRlKAogICAgTW9kYWxpdHkgPSBjYXNlX3doZW4oCiAgICAgIGdyZXBsKCJBVEFDIiwgQXNzYXkuTmFtZSkgfiAiQVRBQyIsCiAgICAgIGdyZXBsKCJSTkEiLCBBc3NheS5OYW1lKSB+ICJSTkEiLAogICAgICBUUlVFIH4gIk90aGVyIgogICAgKQogICkgJT4lCiAgZ3JvdXBfYnkoQ2hhcmFjdGVyaXN0aWNzLmdlbmV0aWMubW9kaWZpY2F0aW9uLikgJT4lCiAgc3VtbWFyaXplKAogICAgQVRBQ19zYW1wbGVzID0gcGFzdGUodW5pcXVlKEFzc2F5Lk5hbWVbTW9kYWxpdHkgPT0gIkFUQUMiXSksIGNvbGxhcHNlID0gIiwgIiksCiAgICBSTkFfc2FtcGxlcyA9IHBhc3RlKHVuaXF1ZShBc3NheS5OYW1lW01vZGFsaXR5ID09ICJSTkEiXSksIGNvbGxhcHNlID0gIiwgIikKICApIApgYGAKCkluIHRoaXMgZGF0YXNldCwgYFMxYCBhbmQgYFMyYCBkZW5vdGUgdGhlIHNhbWUgYmlvbG9naWNhbCBzYW1wbGUgc2VxdWVuY2VkIGFjcm9zcyB0d28gZGlmZmVyZW50IGxhbmVzLiBUaGVyZSBhcmUgdGhyZWUgcGFpcmVkIFJOQStBVEFDIGRhdGFzZXRzOiB0d28gQ1JJU1BSIGtub2Nrb3V0cyBhbmQgb25lIGNvbnRyb2wuIEVhY2ggcGFpciBjb3JyZXNwb25kcyB0byBhIHNwZWNpZmljIHNhbXBsZSBJRDoKCiogQTQ6IENvbnRyb2wgKFdUKQoqIEI0OiBDUklTUFIgS08gd2l0aCBhIDI5IGFuZCA0MSBicCBkZWxldGlvbiBpbiBHTEkzCiogRDM6IENSSVNQUiBLTyB3aXRoIGEgNCBhbmQgOCBicCBpbnNlcnRpb24gaW4gR0xJMwoKKipQUyoqOiBbR0xJM10oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvR0xJMykgaXMgemluYyBmaW5nZXIgcHJvdGVpbiB3aGljaCBpcyBpbnZvbHZlZCBpbiBTb25pYyBoZWRnZWhvZyAoU2hoKSBzaWduYWxpbmcuCgojIyBTdGVwIDIgLSBVbmRlcnN0YW5kIHRoZSBPdXRwdXQgCgpUbyBzdHJlYW1saW5lIHRoZSBhbmFseXNpcywgbXVsdGlwbGUgcnVucyAoaS5lLiwgcmVzdWx0cyBmcm9tIGBjZWxscmFuZ2VyLWFyYyBjb3VudGApIHdlcmUgYWdncmVnYXRlZCB1c2luZyBbYGNlbGxyYW5nZXItYXJjIGFnZ3JgXShodHRwczovL3d3dy4xMHhnZW5vbWljcy5jb20vc3VwcG9ydC9zb2Z0d2FyZS9jZWxsLXJhbmdlci1hcmMvbGF0ZXN0L2FuYWx5c2lzL3J1bm5pbmctcGlwZWxpbmVzL2FnZ3JlZ2F0aW5nLW11bHRpcGxlLWdlbS13ZWxscy1hZ2dyKS4gVGhlIGFnZ3JlZ2F0aW9uIHdhcyBndWlkZWQgYnkgYSBgbGlicmFyaWVzX2FnZ3IuY3N2YCBmaWxlOgoKYGBge3J9CmxpYnJhcmllcyA8LSByZWFkLmNzdignZGF0YS9saWJyYXJpZXNfYWdnci5jc3YnKQpsaWJyYXJpZXMgCmBgYAoKKipLZXkgcG9pbnRzKio6CgoqIEZvciBSTkEgZGF0YSwgYGFnZ3JgIGNvbWJpbmVzIGNvdW50IG1hdHJpY2VzIChub3QgYW4gaW50ZWdyYXRpb24gc3RlcCkuCiogRm9yIEFUQUMgZGF0YSwgYGFnZ3JgIG1lcmdlcyBhbGwgZnJhZ21lbnRzIGFuZCBwZXJmb3JtcyBuZXcgcGVhayBjYWxsaW5nLCBlbnN1cmluZyBhIHVuaWZpZWQgcGVhayBzZXQgKGNoZWNrIFtoZXJlXShodHRwczovL2tiLjEweGdlbm9taWNzLmNvbS9oYy9lbi11cy9hcnRpY2xlcy82MDU3ODkwNTc4ODI5LURvZXMtY2VsbHJhbmdlci1hdGFjLWFnZ3ItcmVkby1wZWFrLWNhbGxpbmcpKS4gCgoqKk5vdGUqKjogQWx0ZXJuYXRpdmVseSwgcGVhayBjYWxsaW5nIGZvciBlYWNoIHNhbXBsZSBjYW4gYmUgcGVyZm9ybWVkIHVzaW5nIHRvb2xzIGxpa2UgW2BNQUNTM2BdKGh0dHBzOi8vbWFjczMtcHJvamVjdC5naXRodWIuaW8vTUFDUy9kb2NzL2NhbGxwZWFrLmh0bWwjKSBvciB0aGUgW2BTaWduYWM6OkNhbGxQZWFrc2BdKGh0dHBzOi8vc3R1YXJ0bGFiLm9yZy9zaWduYWMvYXJ0aWNsZXMvcGVha19jYWxsaW5nKS4gU3Vic2VxdWVudGx5LCB0aGUgdW5pb24vaW50ZXJzZWN0aW9uIG9mIHBlYWtzIGFjcm9zcyBzYW1wbGVzIGNhbiBiZSBzZWxlY3RlZCBmb3IgZG93bnN0cmVhbSBhbmFseXNpcy4KCiMgUGFydCAyIC0gTG9hZCB0aGUgRGF0YQoKIyMgU3RlcCAxIC0gTG9hZCB0aGUgQ291bnQgTWF0cml4CgpTaW1pbGFyIHRvIHRoZSBzdGFuZGFyZCBwaXBlbGluZSBmb3Igc2luZ2xlLWNlbGwgUk5BLXNlcSAoc2NSTkEtc2VxKSBkYXRhLCB3ZSBjYW4gdXNlIHRoZSBvdXRwdXQgZm9sZGVyIFtgZmlsdGVyZWRfZmVhdHVyZV9iY19tYXRyaXhgXShodHRwczovL3d3dy4xMHhnZW5vbWljcy5jb20vc3VwcG9ydC9zb2Z0d2FyZS9jZWxsLXJhbmdlci1hcmMvbGF0ZXN0L2FuYWx5c2lzL3NpbmdsZS1saWJyYXJ5LWFuYWx5c2lzKS4KCmBgYHtyfQpjb3VudF9hZ2dyIDwtIFJlYWQxMFgoImRhdGEvZmlsdGVyZWRfZmVhdHVyZV9iY19tYXRyaXgvIikKYGBgCgpTaW5jZSB0aGlzIGRhdGFzZXQgaW5jbHVkZXMgdHdvIG1vZGFsaXRpZXMgKFJOQSAmIEFUQUMpLCB0aGUgb3V0cHV0IHdpbGwgY29udGFpbiB0d28gbWF0cmljZXMuIFdlIGNhbiBjaGVjayB0aGVpciBkaW1lbnNpb25zIGFzIGZvbGxvd3M6CgpgYGB7ciwgcmVzdWx0cz0ibWFya3VwIn0KZGltKGNvdW50X2FnZ3IkYEdlbmUgRXhwcmVzc2lvbmApCmRpbShjb3VudF9hZ2dyJFBlYWtzKQpgYGAKCkJlZm9yZSBwcm9jZWVkaW5nLCB3ZSBjb25maXJtIHdoZXRoZXIgdGhlIGJhcmNvZGVzIChjZWxsIGlkZW50aXRpZXMpIGluIGJvdGggbWF0cmljZXMgbWF0Y2g6CgpgYGB7ciwgcmVzdWx0cz0ibWFya3VwIn0KYWxsKGNvbG5hbWVzKGNvdW50X2FnZ3IkYEdlbmUgRXhwcmVzc2lvbmApID09IGNvbG5hbWVzKGNvdW50X2FnZ3IkUGVha3MpKQpgYGAKCklmIFRSVUUsIHRoZSBiYXJjb2RlcyBhcmUgcGVyZmVjdGx5IGFsaWduZWQgYWNyb3NzIGJvdGggbW9kYWxpdGllcy4gSWYgRkFMU0UsIGZ1cnRoZXIgaW52ZXN0aWdhdGlvbiBpcyBuZWVkZWQgdG8gaWRlbnRpZnkgZGlzY3JlcGFuY2llcy4KCiMjIFN0ZXAgMiAtIEV4cGxvcmluZyB0aGUgUk5BIEFzc2F5CgpUaGUgUk5BIGFzc2F5IGdlbmVyYXRlcyBhICoqR0VORSDDlyBDRUxMKiogbWF0cml4LCB3aGljaCBhbHJlYWR5IHByb3ZpZGVzIHVzZWZ1bCBpbmZvcm1hdGlvbjoKCmBgYHtyLCByZXN1bHRzPSJtYXJrdXAifQpyb3duYW1lcyhjb3VudF9hZ2dyJGBHZW5lIEV4cHJlc3Npb25gKSAlPiUgaGVhZCgpCmBgYAoKYGBge3IsIHJlc3VsdHM9Im1hcmt1cCJ9CnNldXJhdCA8LSBDcmVhdGVTZXVyYXRPYmplY3QoY291bnRzID0gY291bnRfYWdnciRgR2VuZSBFeHByZXNzaW9uYCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXNzYXkgPSAiUk5BIikKc2V1cmF0CmBgYAoKIyMgU3RlcCAzIC0gRXhwbG9yaW5nIHRoZSBBVEFDIEFzc2F5CgpVbmxpa2UgUk5BIGRhdGEsIGNocm9tYXRpbiBhY2Nlc3NpYmlsaXR5IGRhdGEgaXMgc3RvcmVkIGFzIGEgKipQRUFLIMOXIENFTEwqKiBtYXRyaXg6CgpgYGB7ciwgcmVzdWx0cz0ibWFya3VwIn0Kcm93bmFtZXMoY291bnRfYWdnciRQZWFrcykgJT4lIGhlYWQoKQpgYGAKCkVhY2ggZmVhdHVyZSAocm93KSByZXByZXNlbnRzIGEgZ2Vub21pYyByZWdpb24gKHBlYWspIGluIHRoZSBmb3JtYXQ6IGBjaHJvbW9zb21lOnN0YXJ0LWVuZGAuIEFuZCB3ZSB3aWxsIG5vdGljZSB0aGF0IHRoZSBtYXRyaXggaXMgc3RpbGwgdmVyeSBiaWcuIFRvICoqbm90IGtpbGwgdGhpcyBSIHNlc3Npb24gYmVjYXVzZSBvZiBtZW1vcnkgcmVxdWlyZW1lbnRzKiosIHdlIGNhbiBkbyBzb21lIGZpbHRlcmluZyBmaXJzdDoKCmBgYHtyLCByZXN1bHRzPSJtYXJrdXAifQptaW5fY2VsbHMgPC0gcm91bmQoMC4wNSAqIG5jb2woY291bnRfYWdnciRQZWFrcykpICAjIEtlZXAgcGVha3MgcHJlc2VudCBpbiBhdCBsZWFzdCBYJSBvZiBjZWxscwpmaWx0ZXJlZF9wZWFrcyA8LSBjb3VudF9hZ2dyJFBlYWtzW3Jvd1N1bXMoY291bnRfYWdnciRQZWFrcyA+IDApID49IG1pbl9jZWxscywgXQptaW5fcmVhZHMgPC0gMTAwICAjIEtlZXAgcGVha3Mgd2l0aCBhdCBsZWFzdCBZIHJlYWRzCmZpbHRlcmVkX3BlYWtzIDwtIGZpbHRlcmVkX3BlYWtzW3Jvd1N1bXMoZmlsdGVyZWRfcGVha3MpID49IG1pbl9yZWFkcywgXQpkaW0oZmlsdGVyZWRfcGVha3MpICAjIENoZWNrIG5ldyBkaW1lbnNpb25zCmBgYAoKVG8gYmV0dGVyIGludGVycHJldCB0aGUgcGVha3MsIHdlIGNhbiBhbm5vdGF0ZSB0aGVtIHdpdGggdGhlICoqbmVhcmVzdCoqIGdlbmVzLiBNYW55IGdlbm9taWMgZGF0YWJhc2VzIGFuZCB0b29scyBwcm92aWRlIFIgaW50ZXJmYWNlcywgbWFraW5nIFIgYSBwb3dlcmZ1bCBlbnZpcm9ubWVudCBmb3IgZ2Vub21pYyBkYXRhIGFuYWx5c2lzLiBIZXJlIHdlIGZvbGxvdyB0aGUgW2BTZXVyYXRgIFdOTiB0dXRvcmlhbF0oaHR0cHM6Ly9zYXRpamFsYWIub3JnL3NldXJhdC9hcnRpY2xlcy93ZWlnaHRlZF9uZWFyZXN0X25laWdoYm9yX2FuYWx5c2lzI3dubi1hbmFseXNpcy1vZi0xMHgtbXVsdGlvbWUtcm5hLWF0YWMpOgoKYGBge3IsIHdhcm5pbmc9RkFMU0V9CmFubm90YXRpb25zIDwtIEdldEdSYW5nZXNGcm9tRW5zRGIoZW5zZGIgPSBFbnNEYi5Ic2FwaWVucy52ODYpCnNlcWxldmVsc1N0eWxlKGFubm90YXRpb25zKSA8LSAnVUNTQycKZ2Vub21lKGFubm90YXRpb25zKSA8LSAiaGczOCIKYGBgCgpgYGB7cn0KIyMjIyMjIyMjIyMjIyMjIyBOT1RFICMjIyMjIyMjIyMjIyMjIyMjCiMjIFN0cmluZ1RvR1JhbmdlcyBrZWVwIGNydXNoaW5nICg/KSAjIyMKIyMgU28gd2UgZG8gaXQgbWFudWFsbHkgIyMKIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwoKIyBncmFuZ2UuY291bnRzIDwtIFN0cmluZ1RvR1Jhbmdlcyhyb3duYW1lcyhmaWx0ZXJlZF9wZWFrcyksIHNlcCA9IGMoIjoiLCAiLSIpKQoKIyByb3duYW1lcyhmaWx0ZXJlZF9wZWFrcykgY29udGFpbnMgY2hyb21vc29tZSBjb29yZGluYXRlcyBpbiAnY2hyOnN0YXJ0LWVuZCcgZm9ybWF0CnBlYWtfbmFtZXMgPC0gcm93bmFtZXMoZmlsdGVyZWRfcGVha3MpICAjIEV4YW1wbGU6IGMoImNocjE6MS0xMCIsICJjaHIyOjEyLTMxMjEiKQoKIyBTcGxpdCBiYXNlZCBvbiAiOiIgZmlyc3QgKHRvIHNlcGFyYXRlICdjaHInIGZyb20gJ3N0YXJ0LWVuZCcpCnNwbGl0X3BlYWtzIDwtIHN0cnNwbGl0KHBlYWtfbmFtZXMsICI6IikKCiMgRXh0cmFjdCBjaHJvbW9zb21lIG5hbWVzCmNociA8LSBzYXBwbHkoc3BsaXRfcGVha3MsIGBbYCwgMSkKCiMgRnVydGhlciBzcGxpdCB0aGUgc2Vjb25kIHBhcnQgKHN0YXJ0LWVuZCkgYnkgIi0iCnJhbmdlcyA8LSBzYXBwbHkoc3BsaXRfcGVha3MsIGBbYCwgMikKcmFuZ2VzX3NwbGl0IDwtIHN0cnNwbGl0KHJhbmdlcywgIi0iKQoKIyBFeHRyYWN0IHN0YXJ0IGFuZCBlbmQgcG9zaXRpb25zIGFzIGludGVnZXJzCnN0YXJ0IDwtIGFzLmludGVnZXIoc2FwcGx5KHJhbmdlc19zcGxpdCwgYFtgLCAxKSkKZW5kIDwtIGFzLmludGVnZXIoc2FwcGx5KHJhbmdlc19zcGxpdCwgYFtgLCAyKSkKCiMgQ3JlYXRlIEdSYW5nZXMgb2JqZWN0CmdyYW5nZS5jb3VudHMgPC0gR1JhbmdlcyhzZXFuYW1lcyA9IGNociwgcmFuZ2VzID0gSVJhbmdlcyhzdGFydCA9IHN0YXJ0LCBlbmQgPSBlbmQpKQoKIyBXZSdsbCBvbmx5IHVzZSBwZWFrcyBpbiBzdGFuZGFyZCBjaHJvbW9zb21lcwpncmFuZ2UudXNlIDwtIHNlcW5hbWVzKGdyYW5nZS5jb3VudHMpICVpbiUgc3RhbmRhcmRDaHJvbW9zb21lcyhncmFuZ2UuY291bnRzKQpmaWx0ZXJlZF9wZWFrcyA8LSBmaWx0ZXJlZF9wZWFrc1thcy52ZWN0b3IoZ3JhbmdlLnVzZSksIF0KYGBgCgpXZSBjYW4gY2hlY2sgdGhlIGZpbHRlcmVkIG1hdHJpeCBkaW1lbnNpb246CgpgYGB7ciwgcmVzdWx0cz0ibWFya3VwIn0KZGltKGZpbHRlcmVkX3BlYWtzKQpgYGAKCioqSW1wb3J0YW50Kio6IFRoZXJlIGFyZSBvdGhlciB3YXlzIG9mIGhvdyB3ZSBjb3VsZCByZWR1Y2UgdGhlIG51bWJlciBvZiBgcGVha3NgLiBGb3IgZXhhbXBsZSwgYXMgbWVudGlvbmVkIGFib3ZlLCBpZiB5b3UgYXJlIHVzaW5nIGBjZWxscmFuZ2VyLWFyYyBjb3VudGAgb3V0cHV0cyBkaXJlY3RseSwgeW91IGNhbiBtYW51YWxseSBjdXJhdGUgYSBsaXN0IG9mIHBlYWtzIHRoYXQgYXJlIHByZXNlbnQgaW4gbXVsdGlwbGUgc2FtcGxlcyAoY2hlY2sgW2hlcmVdKGh0dHBzOi8vZ2l0aHViLmNvbS9zdHVhcnQtbGFiL3NpZ25hYy9kaXNjdXNzaW9ucy80NjEpKS4gRm9yIGxhcmdlIGRhdGFzZXRzLCBjb25zaWRlciBsZXZlcmFnaW5nIEhQQyBjbHVzdGVycyB3aXRoIGhpZ2hlciBtZW1vcnkgY2FwYWNpdGllcy4KCmBgYHtyfQpmcmFnLmZpbGUgPC0gImRhdGEvYXRhY19mcmFnbWVudHMudHN2Lmd6IgojIEZpbmFsbHkgY3JlYXRlIHRoZSBBVEFDIGFzc2F5CnNldXJhdFtbJ0FUQUMnXV0gPC0gIENyZWF0ZUNocm9tYXRpbkFzc2F5KAogICBjb3VudHMgPSBmaWx0ZXJlZF9wZWFrcywKICAgcmFuZ2VzID0gZ3JhbmdlLmNvdW50c1tncmFuZ2UudXNlXSwKICAgZ2Vub21lID0gJ2hnMzgnLAogICBmcmFnbWVudHMgPSBmcmFnLmZpbGUsCiAgIGFubm90YXRpb24gPSBhbm5vdGF0aW9ucwogKQpgYGAKTm93IHdlIGhhdmUgdGhlIHNldXJhdCBvYmplY3Qgd2l0aCBib3RoIFJOQSBhbmQgQVRBQyBhc3NheXM6CgpgYGB7ciwgcmVzdWx0cz0ibWFya3VwIn0Kc2V1cmF0CmBgYAoKKipPcHRpb25hbCoqOiBTYXZlIHRoZSBvYmplY3QuCgpgYGB7ciwgZXZhbD1GQUxTRX0Kc2F2ZVJEUygKICBvYmplY3QgPSBzZXVyYXQsCiAgZmlsZSA9ICJvdXQvcmF3X3NldXJhdC5SZHMiCikKYGBgCgojIFBhcnQgMyAtIE1vbm9tb2RhbCBEYXRhIEFuYWx5c2lzIAoKIyMgU3RlcCAxIC0gUXVhbGl0eSBDb250cm9sCgpUaGlzIHNlY3Rpb24gaXMgbGFyZ2VseSBpbnNwaXJlZCBmcm9tIFt0aGlzIHR1dG9yaWFsXShodHRwczovL2dpdGh1Yi5jb20vcXVhZGJpby9zY011bHRpb21lX2FuYWx5c2lzX3ZpZ25ldHRlL2Jsb2IvbWFpbi9UdXRvcmlhbC5tZCNzdGVwLTItcXVhbGl0eS1jb250cm9sKS4KCmBgYHtyLCBldmFsPUZBTFNFfQojIGlmIHlvdSBzdGFydCBmcm9tIHRoZSBwcmVwYXJlZCBzZXVyYXQgb2JqZWN0IApzZXVyYXQgPC0gcmVhZFJEUygnb3V0L3Jhd19zZXVyYXQuUmRzJykKYGBgCgpGb3IgdGhlIFJOQSBhc3NheSwgd2hhdCB3ZSB3b3VsZCBjaGVjayBpcyB0aGUgc2FtZSBhcyB0aGUgdHlwaWNhbCBzY1JOQS1zZXEgKGkuZS4sIGZpbHRlciBvdXQgY2VsbHMgd2l0aCB0b28gZmV3IG9yIHRvbyBtYW55IGRldGVjdGVkIGdlbmVzIG9yIHRyYW5zY3JpcHRzIChVTUlzKSkuCgpgYGB7cn0Kc2V1cmF0IDwtIFBlcmNlbnRhZ2VGZWF0dXJlU2V0KHNldXJhdCwgcGF0dGVybiA9ICJeTVQtIiwgY29sLm5hbWUgPSAicGVyY2VudC5tdCIsIGFzc2F5ID0gIlJOQSIpCmBgYAoKRm9yIHRoZSBBVEFDIGFzc2F5LCB3ZSB3b3VsZCBhbHNvIGxvb2sgYXQgdGhlIG51bWJlciBvZiBkZXRlY3RlZCBwZWFrcyBvciBkZXRlY3RlZCBmcmFnbWVudHMsIHNpbWlsYXIgdG8gdGhlIFJOQSBhc3NheS4gT24gdG9wIG9mIHRoYXQsIHdlIHdvdWxkIGFsc28gZXhjbHVkZSBjZWxscyB3aXRoIHRvbyB3ZWFrIGZyYWdtZW50IGVucmljaG1lbnQgYXJvdW5kIHRoZSB0cmFuc2NyaXB0aW9uYWwgc3RhcnQgc2l0ZXMgKFRTUykuIFdlIGNhbiBhbHNvIHF1YW50aWZ5IHRoZSBhcHByb3hpbWF0ZSByYXRpbyBvZiBBVEFDIGZyYWdtZW50cyB3aXRoIGEgc3Ryb25nIG51Y2xlb3NvbWUgYmFuZGluZyBwYXR0ZXJuICh0aG9zZSB3aXRoIGZyYWdtZW50IGxlbmd0aHMgYXJvdW5kIGEgc2luZ2xlIG51Y2xlb3NvbWUpIHRoYXQgdW5saWtlbHkgcmVwcmVzZW50IHJlYWwgYWNjZXNzaWJsZSBnZW5vbWljIHJlZ2lvbnMsIGluIHJlbGF0aXZlIHRvIHRoZSBudWNsZW9zb21lLWZyZWUgZnJhZ21lbnRzLCBhbmQgdGhlbiBkaXNjYXJkIGNlbGxzIHdpdGggdG9vIGhpZ2ggb2Ygc3VjaCByYXRpby4KCmBgYHtyfQpzZXVyYXQgPC0gTnVjbGVvc29tZVNpZ25hbChzZXVyYXQsIGFzc2F5ID0gIkFUQUMiKQpgYGAKCkZvciBUU1MgZW5yaWNobWVudCwgd2UgZG8gc29tZSB0cmlja3MgaGVyZSB0byByZWR1Y2UgdGhlIGNvbXB1dGluZyB0aW1lOiAKCmBgYHtyfQojIEV4dHJhY3QgVFNTIGFubm90YXRpb24KdHNzIDwtIEdldFRTU1Bvc2l0aW9ucyhBbm5vdGF0aW9uKHNldXJhdEBhc3NheXMkQVRBQykpCnNldXJhdCA8LSBUU1NFbnJpY2htZW50KHNldXJhdCx0c3MucG9zaXRpb25zPXNhbXBsZSh0c3MsMTAwMCksYXNzYXkgPSAiQVRBQyIpCmBgYAoKQWZ0ZXIgYWdncmVnYXRpb24sIGJhcmNvZGVzIGFyZSB0eXBpY2FsbHkgYXBwZW5kZWQgd2l0aCBhIHN1ZmZpeCBpbmRpY2F0aW5nIHRoZWlyIG9yaWdpbiAoZS5nLiwgLTEsIC0yLCAtMykuIFlvdSBjYW4gdXNlIHRoZXNlIHN1ZmZpeGVzIHRvIGFzc2lnbiBzYW1wbGUgaWRlbnRpdGllczoKCmBgYHtyfQojIEV4dHJhY3Qgc2FtcGxlIGlkZW50aWZpZXJzIGZyb20gY2VsbCBiYXJjb2RlcwpzZXVyYXQkc2FtcGxlIDwtIHNhcHBseShzdHJzcGxpdChjb2xuYW1lcyhzZXVyYXQpLCAiLSIpLCBgW2AsIDIpCnNldXJhdCRzYW1wbGUgPC0gcGx5cjo6bWFwdmFsdWVzKAogIHNldXJhdCRzYW1wbGUsCiAgZnJvbSA9IGMoIjEiLCAiMiIsICIzIiksCiAgdG8gPSBjKCJXVCIsICJLTzEiLCAiS08yIikKKQpgYGAKCldlIGNhbiBoYXZlIGEgbG9vayBhdCB0aGUgUUMgcmVzdWx0cyBmb3IgZWFjaCBzYW1wbGUgaGVyZTogCgpgYGB7cn0KVmxuUGxvdChzZXVyYXQsCiAgICAgICAgZmVhdHVyZXMgPSBjKCJuRmVhdHVyZV9STkEiLAogICAgICAgICAgICAgICAgICAgICAicGVyY2VudC5tdCIsCiAgICAgICAgICAgICAgICAgICAgICJuRmVhdHVyZV9BVEFDIiwKICAgICAgICAgICAgICAgICAgICAgIlRTUy5lbnJpY2htZW50IiwKICAgICAgICAgICAgICAgICAgICAgIm51Y2xlb3NvbWVfc2lnbmFsIiksCiAgICAgICAgbmNvbCA9IDMsIGdyb3VwLmJ5PSdzYW1wbGUnLAogICAgICAgIHB0LnNpemUgPSAwKQpgYGAKCkJhc2VkIG9uIHRoZSBkaXN0cmlidXRpb25zLCB3ZSBjYW4gc2V0IHRoZSBmaWx0ZXJpbmcgY3JpdGVyaWEgYXM6CgpgYGB7cn0Kc2V1cmF0IDwtIHN1YnNldChzZXVyYXQsCiAgc3Vic2V0ID0gbkZlYXR1cmVfUk5BID4gMTAwMCAmCiAgICBuRmVhdHVyZV9STkEgPCA3NTAwICYKICAgIHBlcmNlbnQubXQgPCA0MCAmCiAgICBuRmVhdHVyZV9BVEFDID4gMTAwMCAmCiAgICBuRmVhdHVyZV9BVEFDIDwgMjUwMDAgJgogICAgVFNTLmVucmljaG1lbnQgPiAxICYKICAgIG51Y2xlb3NvbWVfc2lnbmFsIDwgMQopCmBgYAoKIyMgU3RlcCAyIC0gUk5BIEFzc2F5IAoKVGhlIGFuYWx5c2lzIGZvciB0aGUgc2NSTkEgYXNzYXkgaXMgcmF0aGVyIHN0YW5kYXJkLCBpbmNsdWRpbmcgZGF0YSBub3JtYWxpemF0aW9uLCBoaWdobHkgdmFyaWFibGUgZ2VuZXMgaWRlbnRpZmljYXRpb24sIGRhdGEgc2NhbGluZywgcHJpbmNpcGFsIGNvbXBvbmVudCBhbmFseXNpcyAoUENBKSwgYW5kIHRoZW4gdGhlIFVNQVAgZW1iZWRkaW5nLiBBZGRpdGlvbmFsbHksIHdlIHVzZSB0aGUgQ0NBIGludGVncmF0aW9uIGZyb20gYHNldXJhdGAgZm9sbG93aW5nIHRoaXMgW3R1dG9yaWFsXShodHRwczovL3NhdGlqYWxhYi5vcmcvc2V1cmF0L2FydGljbGVzL2ludGVncmF0aW9uX2ludHJvZHVjdGlvbi5odG1sI3BlcmZvcm0taW50ZWdyYXRpb24pOgoKYGBge3J9CkRlZmF1bHRBc3NheShzZXVyYXQpIDwtICJSTkEiCiMgdG8gbWFrZSBpdCB3b3JrIHdpdGggc2V1cmF0IHY1CnNldXJhdFtbJ1JOQSddXSA8LSBzcGxpdChzZXVyYXRbWyJSTkEiXV0sIGYgPSBzZXVyYXQkc2FtcGxlKQpzZXVyYXQgPC0gTm9ybWFsaXplRGF0YShzZXVyYXQpICU+JQogIEZpbmRWYXJpYWJsZUZlYXR1cmVzKG5mZWF0dXJlcyA9IDMwMDApICU+JQogIFNjYWxlRGF0YSgpICU+JQogIFJ1blBDQShucGNzID0gNTApIAoKc2V1cmF0IDwtIEludGVncmF0ZUxheWVycyhvYmplY3QgPSBzZXVyYXQsIG1ldGhvZCA9IENDQUludGVncmF0aW9uLAogICAgICAgICAgICAgICAgICBvcmlnLnJlZHVjdGlvbiA9ICJwY2EiLCBuZXcucmVkdWN0aW9uID0gImludGVncmF0ZWQuY2NhIiwKICAgICAgICAgICAgICAgICAgdmVyYm9zZSA9IEZBTFNFKQoKc2V1cmF0W1snUk5BJ11dIDwtIEpvaW5MYXllcnMoc2V1cmF0W1snUk5BJ11dKQpzZXVyYXQgPC0gUnVuVU1BUChzZXVyYXQsIGRpbXMgPSAxOjMwLCByZWR1Y3Rpb24gPSAiaW50ZWdyYXRlZC5jY2EiLAogICAgICAgICAgICAgICAgcmVkdWN0aW9uLm5hbWUgPSAidW1hcF9ybmEiLCByZWR1Y3Rpb24ua2V5ID0gIlVNQVBJTlRFR1JBVEVEQ0NBUk5BXyIpCmBgYAoKRm9yIHZpc3VhbGl6YXRpb24sIHdlIHVzZSB0aGUgbWFya2VyIGdlbmVzIG1lbnRpb25lZCBpbiB0aGlzIFt0dXRvcmlhbF0oaHR0cHM6Ly9naXRodWIuY29tL3F1YWRiaW8vc2NSTkFzZXFfYW5hbHlzaXNfdmlnbmV0dGUvYmxvYi9tYXN0ZXIvVHV0b3JpYWwubWQjc3RlcC05LWFubm90YXRlLWNlbGwtY2x1c3RlcnMpLiAgCgpgYGB7cn0KIyBWaXN1YWxpemF0aW9uCm1hcmtlcl9nZW5lcyA8LSBjKCJNS0k2NyIsIk5FUyIsIkRDWCIsIkZPWEcxIiwiRExYMiIsIkVNWDEiLCJPVFgyIiwiTEhYOSIsIlRGQVAyQSIpCgpwMSA8LSBEaW1QbG90KHNldXJhdCwgZ3JvdXAuYnkgPSAic2FtcGxlIiwgcmVkdWN0aW9uID0gInVtYXBfcm5hIikgJiBOb0F4ZXMoKQpwMiA8LSBGZWF0dXJlUGxvdChzZXVyYXQsCiAgICAgICAgICAgICAgICAgIG1hcmtlcl9nZW5lcywKICAgICAgICAgICAgICAgICAgcmVkdWN0aW9uID0gInVtYXBfcm5hIikgJiBOb0F4ZXMoKSAmIE5vTGVnZW5kKCkKcDEgfCBwMgpgYGAKCldlIGNhbiBhbHJlYWR5IHNlZSB0aGF0IHRoZSAyIEtPcyBhbmQgV1QgYXJlIG5pY2VseSBpbnRlZ3JhdGVkLiBGcm9tIG1ha2VyIGdlbmVzIHdlIGNhbiBhbHNvIHNlZSB0aGUgc2VwYXJhdGlvbiBvZiBwcm9nZW5pdG9ycyAoZS5nLiBORVMpIGFuZCBuZXVyb25zIChlLmcuIERDWCksIGFzIHdlbGwgYXMgYnJhaW4gcmVnaW9uYWwgaWRlbnRpdGllcyAoZS5nLiBGT1hHMSkuIFdlIGNhbiBhbHNvIGRvIGNsdXN0ZXJpbmcgYW5hbHlzaXMgYmFzZWQgb24gUk5Bc2VxIGRhdGEgYWxvbmU6CgpgYGB7cn0Kc2V1cmF0IDwtIEZpbmROZWlnaGJvcnMoc2V1cmF0LCByZWR1Y3Rpb24gPSAiaW50ZWdyYXRlZC5jY2EiLCBkaW1zID0gMTozMCkKIyBGaW5kIHRoZSBvcHRpbXVtIGNsdXN0ZXIgcmVzb2x1dGlvbgpzZXVyYXQgPC0gRmluZENsdXN0ZXJzKHNldXJhdCwgcmVzb2x1dGlvbiA9IHNlcSgwLjEsMSxieT0wLjEpLCB2ZXJib3NlID0gRiwgZ3JhcGgubmFtZSA9ICdSTkFfc25uJykKYGBgCgpXZSBjYW4gdXNlIGBjbHVzdHJlZWAgdG8gYnVpbGQgYSBjbHVzdGVyaW5nIHRyZWUsIHdoaWNoIGlzIHVzZWZ1bCB0byB2aXN1YWxpemUgdGhlIHJlbGF0aW9uc2hpcHMgYmV0d2VlbiBjbHVzdGVyaW5nIHJlc29sdXRpb25zLiAKCmBgYHtyLGZpZy53aWR0aD0xMCxmaWcuaGVpZ2h0PTZ9CiMgTWFrZSBQbG90CmNsdXN0cmVlKHNldXJhdCwgbGF5b3V0PSJzdWdpeWFtYSIsIHByZWZpeCA9ICJSTkFfc25uX3Jlcy4iKQpgYGAKCioqTk9URSoqOiBUaGVyZSBhcmUgaW5kZWVkIG1hbnkgb3RoZXIgY3JpdGVyaWEgYW5kIGFsZ29yaXRobXMgdG8gZXZhbHV0ZSB0aGUgbnVtYmVyIG9mIGNsdXN0ZXJzLiBIZXJlIHRvIG1ha2UgdGhpbmdzIHNpbXBsZSwgd2UganVzdCBjaG9vc2Ugb25lIHJlc29sdXRpb24gd2hlcmUgdGhlIGNsdXN0ZXJzIGFyZSByZWxhdGl2ZWx5IHN0YWJsZS4KCmBgYHtyfQpwMSA8LSBEaW1QbG90KHNldXJhdCwgZ3JvdXAuYnkgPSAiUk5BX3Nubl9yZXMuMC43IiwgCiAgICAgICAgICAgICAgcmVkdWN0aW9uID0gInVtYXBfcm5hIiwgbGFiZWwgPSBUKSAmIE5vQXhlcygpICYgTm9MZWdlbmQoKQpwMSB8IHAyCmBgYAoKCiMjIFN0ZXAgMyAtIEFUQUMgQXNzYXkKCkFUQUMtc2VxIGRhdGEgYW5hbHlzaXMgZm9sbG93cyBhIHNpbWlsYXIgd29ya2Zsb3cgdG8gc2NSTkEtc2VxLCBpbmNsdWRpbmcgbm9ybWFsaXphdGlvbiwgZmVhdHVyZSBzZWxlY3Rpb24sIGxpbmVhciBhbmQgbm9uLWxpbmVhciBkaW1lbnNpb25hbCByZWR1Y3Rpb24sIGFuZCBkYXRhIGludGVncmF0aW9uLiBIb3dldmVyLCBkdWUgdG8gZGlmZmVyZW5jZXMgaW4gZGF0YSBwcm9wZXJ0aWVzLCB0aGUgc3BlY2lmaWMgYWxnb3JpdGhtcyB1c2VkIGluIGVhY2ggc3RlcCB2YXJ5LgoKVGhpcyBzZWN0aW9uIHByaW1hcmlseSBmb2xsb3dzIHRoZSBbb2ZmaWNpYWwgdHV0b3JpYWwgZnJvbSBgU2lnbmFjYF0oaHR0cHM6Ly9zdHVhcnRsYWIub3JnL3NpZ25hYy9hcnRpY2xlcy9wYm1jX3ZpZ25ldHRlLmh0bWwjbm9ybWFsaXphdGlvbi1hbmQtbGluZWFyLWRpbWVuc2lvbmFsLXJlZHVjdGlvbikgdG9nZXRoZXIgd2l0aCB0aGlzIFttdWx0aW9tZSBhbmFseXNpcyB0dXRvcmlhbF0oaHR0cHM6Ly9naXRodWIuY29tL3F1YWRiaW8vc2NNdWx0aW9tZV9hbmFseXNpc192aWduZXR0ZS9ibG9iL21haW4vVHV0b3JpYWwubWQjc3RlcC00LWFuYWx5c2lzLW9uLXRoZS1hdGFjLWFzc2F5KS4KCiMjIyBTdGVwIDMuMSAtIE5vcm1hbGl6YXRpb24gYW5kIExpbmVhciBEaW1lbnNpb25hbCBSZWR1Y3Rpb24KCioqRmVhdHVyZSBzZWxlY3Rpb24qKjpUaGUgbG93IGR5bmFtaWMgcmFuZ2Ugb2Ygc2NBVEFDLXNlcSBkYXRhIG1ha2VzIGl0IGNoYWxsZW5naW5nIHRvIHBlcmZvcm0gZmVhdHVyZSBzZWxlY3Rpb24gYmFzZWQgb24gdmFyaWFibGl0eSBhcyBpbiBzY1JOQS1zZXEuIEluc3RlYWQsIHdlIGNhbiBjaG9vc2UgdG8gdXNlIG9ubHkgdGhlIHRvcCBuJSBvZiBmZWF0dXJlcyAocGVha3MpIGZvciBkaW1lbnNpb25hbCByZWR1Y3Rpb24sIG9yIHJlbW92ZSBmZWF0dXJlcyBwcmVzZW50IGluIGxlc3MgdGhhbiBuIGNlbGxzIChzaW1pbGFyIHRvIHdoYXQgd2UgZGlkIHdoZW4gY3JlYXRpbmcgdGhpcyBzZXVyYXQgb2JqZWN0KS4KCmBgYHtyfQpEZWZhdWx0QXNzYXkoc2V1cmF0KSA8LSAiQVRBQyIKIyBzZWxlY3QgdGhlIHRvcCAyNSUgbW9zdCBjb21tb24gZmVhdHVyZXMgZm9yIGZhc3RlciBydW50aW1lCnNldXJhdCA8LSBGaW5kVG9wRmVhdHVyZXMoc2V1cmF0LCBtaW4uY3V0b2ZmID0gJ3E3NScpIApgYGAKCioqTm9ybWFsaXphdGlvbioqOiBTaWduYWMgcGVyZm9ybXMgVGVybSBGcmVxdWVuY3ktSW52ZXJzZSBEb2N1bWVudCBGcmVxdWVuY3kgKFtURi1JREZdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL1Rm4oCTaWRmKSkgbm9ybWFsaXphdGlvbi4gVGhpcyBpcyBhIHR3by1zdGVwIG5vcm1hbGl6YXRpb24gcHJvY2VkdXJlOgoKKiBUZXJtIGZyZXF1ZW5jeSAoVEYpIG5vcm1hbGl6ZXMgZm9yIHNlcXVlbmNpbmcgZGVwdGggYnkgc2NhbGluZyBwZWFrIGFjY2Vzc2liaWxpdHkgY291bnRzIHdpdGhpbiBlYWNoIGNlbGwKKiBJbnZlcnNlIERvY3VtZW50IEZyZXF1ZW5jeSAoSURGKSBkb3duLXdlaWdodHMgdGhlc2UgZnJlcXVlbnRseSBhY2Nlc3NpYmxlIHJlZ2lvbnMgKGkuZSwgcGVha3MgdGhhdCBhcmUgb3BlbiBhY3Jvc3MgbWFueSBjZWxscyBidXQgbWlnaHQgbm90IGJlIGJpb2xvZ2ljYWxseSBpbmZvcm1hdGl2ZSksIGVuc3VyaW5nIHRoYXQgcmFyZSBidXQgY2VsbC10eXBlLXNwZWNpZmljIHBlYWtzIGdldCBtb3JlIGltcG9ydGFuY2UuCgpgYGB7cn0Kc2V1cmF0IDwtIFJ1blRGSURGKHNldXJhdCkKYGBgCgoqKkxpbmVhciBkaW1lbnNpb24gcmVkdWN0aW9uKio6IFdlIG5leHQgcnVuIFNpbmd1bGFyIFZhbHVlIERlY29tcG9zaXRpb24gKFNWRCkgdG8gdGhlIFRELUlERiBtYXRyaXgsIHdoaWNoIGdpdmVzIExhdGVudCBTZW1hbnRpYyBJbmRleGluZyAoTFNJKSBjb21wb25lbnRzLiBUaGlzIGlzIHZlcnkgc2ltaWxhciB0byBQQ0EsIGJ1dCBpdCBjYW4gYmV0dGVyIGhhbmRsZSB0aGUgc3BhcnNpdHkgKGFzIHdlIGNhbiBzZWUgQVRBQyBtYXRyaXggaXMgbXVjaCBiaWdnZXIgdGhhbiB0aGUgUk5BIG9uZSkuIFRoZSBmaXJzdCBzaW5ndWxhciB2ZWN0b3Igb2Z0ZW4gY2FwdHVyZXMgc2VxdWVuY2luZyBkZXB0aCAodGVjaG5pY2FsIHZhcmlhdGlvbikgcmF0aGVyIHRoYW4gYmlvbG9naWNhbCB2YXJpYXRpb24uIAoKYGBge3J9CnNldXJhdCA8LSBSdW5TVkQoc2V1cmF0LCBuID0gNTApCiMgY2hlY2sgd2hhdCB0aGUgTFNJIGRpbWVuc2lvbnMgZXhwbGFpbgpwMSA8LSBFbGJvd1Bsb3Qoc2V1cmF0LCBuZGltcyA9IDMwLCByZWR1Y3Rpb249ImxzaSIpCnAyIDwtIERlcHRoQ29yKHNldXJhdCwgbiA9IDMwKSAjIGNvcnJlbGF0aW9uIHRvIHNlcXVlbmNpbmcgZGVwdGggCnAxIHwgcDIKYGBgCgoqKk5vbi1saW5lYXIgZGltZW5zaW9uIHJlZHVjdGlvbioqOiBTaW1pbGFyIHRvIHNjUk5BLXNlcSBkYXRhLCB3ZSBjYW4gdmlzdWFsaXplIHRoZSBkYXRhIHVzaW5nIFVNQVA6CgpgYGB7cn0Kc2V1cmF0IDwtIFJ1blVNQVAoc2V1cmF0LAogICAgICAgICAgICAgICAgICByZWR1Y3Rpb24gPSAibHNpIiwKICAgICAgICAgICAgICAgICAgZGltcyA9IDI6MzAsCiAgICAgICAgICAgICAgICAgIHJlZHVjdGlvbi5uYW1lID0gInVtYXBfYXRhYyIsCiAgICAgICAgICAgICAgICAgIHJlZHVjdGlvbi5rZXkgPSAiVU1BUEFUQUNfIikKcDEgPC0gRGltUGxvdChzZXVyYXQsCiAgICAgICAgICAgICAgZ3JvdXAuYnkgPSAic2FtcGxlIiwKICAgICAgICAgICAgICByZWR1Y3Rpb24gPSAidW1hcF9hdGFjIikgJiBOb0F4ZXMoKQpwMiA8LSBGZWF0dXJlUGxvdChzZXVyYXQsCiAgICAgICAgICAgICAgICAgIG1hcmtlcl9nZW5lcywKICAgICAgICAgICAgICAgICAgcmVkdWN0aW9uID0gInVtYXBfYXRhYyIpICYgTm9BeGVzKCkgJiBOb0xlZ2VuZCgpCnAxIHwgcDIKYGBgCgpIZXJlLCBib3RoIHBhbmVscyBhcmUgc2hvd24gaW4gdGhlIFVNQVAgc3BhY2UgZGVyaXZlZCBmcm9tIEFUQUMgZGF0YS4gT24gdGhlIGxlZnQsIHdlIG9ic2VydmUgYSByZWxhdGl2ZWx5IHVuaWZvcm0g4oCcYmxvYi1saWtl4oCdIHN0cnVjdHVyZSBjb21wYXJpbmcgdG8gdGhlIFVNQVAgc3BhY2UgZnJvbSBSTkEgYXNzYXkuIEhvd2V2ZXIsIHdoZW4gUk5BIGV4cHJlc3Npb24gZnJvbSB0aGUgbXVsdGlvbWUgaXMgb3ZlcmxhaWQgb24gdGhlIHNhbWUgQVRBQyBVTUFQIChyaWdodCksIHdlIGNhbiBzdGlsbCBzZWUgYSAgc2VwYXJhdGlvbiBiZXR3ZWVuIHByb2dlbml0b3JzIGFuZCBuZXVyb25zIChlLmcuLCBNS0k2NywgTkVTLCBEQ1gpLCBhcyB3ZWxsIGFzIGV2aWRlbmNlIG9mIGJyYWluIHJlZ2lvbmFsaXphdGlvbiAoZS5nLiwgRk9YRzEsIERMWDIsIEVNWDEsIE9UWDIsIExIWDIsIFRGQVAyQSksIGluZGljYXRpbmcgdGhhdCBSTkEgcmVtYWlucyB0aGUgZG9taW5hbnQgc291cmNlIG9mIGluZm9ybWF0aW9uIGZvciBjZWxsIGlkZW50aXR5IGFuZCBzdGF0ZS4gCgojIyMgKE9wdGlvbmFsKSBTdGVwIDMuMiBDcmVhdGUgYSBnZW5lIGFjdGl2aXR5IG1hdHJpeAoKVG8gZXN0aW1hdGUgZ2VuZSBhY3Rpdml0eSwgd2UgYXNzZXNzIGNocm9tYXRpbiBhY2Nlc3NpYmlsaXR5IGFzc29jaWF0ZWQgd2l0aCBlYWNoIGdlbmUgYW5kIGdlbmVyYXRlIGFuIGluZmVycmVkIFJOQSBhc3NheSBmcm9tIHRoZSBzY0FUQUMtc2VxIGRhdGEuIFRoaXMgY2FuIGJlIGRvbmUgdXNpbmcgYSB3cmFwcGVyIGZ1bmN0aW9uIGBHZW5lQWN0aXZpdHkoKWA6CgpgYGB7cn0KZ2VuZS5hY3Rpdml0aWVzIDwtIEdlbmVBY3Rpdml0eShzZXVyYXQpCnNldXJhdFtbJ1JOQV9pbmZlcnJlZCddXSA8LSBDcmVhdGVBc3NheU9iamVjdChnZW5lLmFjdGl2aXRpZXMpICU+JSBOb3JtYWxpemVEYXRhKCkKCkRlZmF1bHRBc3NheShzZXVyYXQpIDwtICJSTkFfaW5mZXJyZWQiCnAzIDwtIEZlYXR1cmVQbG90KHNldXJhdCwKICAgICAgICAgICAgICAgICAgbWFya2VyX2dlbmVzLAogICAgICAgICAgICAgICAgICByZWR1Y3Rpb24gPSAidW1hcF9hdGFjIikgJiBOb0F4ZXMoKSAmIE5vTGVnZW5kKCkKcDEgfCBwMwpgYGAKSGVyZSwgd2Ugc2hvdyBnZW5lIGFjdGl2aXR5IHNjb3JlcyBpbmZlcnJlZCBmcm9tIEFUQUMgcHJvZmlsZXMuIFdoaWxlIHRoZSBhY3Rpdml0eSBwYXR0ZXJucyBmb3Igc29tZSBnZW5lcywgc3VjaCBhcyBGT1hHMSwgcGFydGlhbGx5IHJlY2FwaXR1bGF0ZSB0aGUgZXhwcmVzc2lvbiBwYXR0ZXJucyBvYnNlcnZlZCBpbiB0aGUgUk5BIGFzc2F5IChwcmV2aW91cyBmaWd1cmUpLCB0aGUgb3ZlcmFsbCBzZXBhcmF0aW9uIGJldHdlZW4gY2VsbCB0eXBlcyBpcyBsZXNzIGRpc3RpbmN0LiBUaGlzIGhpZ2hsaWdodHMgdGhhdCB3aGlsZSBnZW5lIGFjdGl2aXR5IGZyb20gQVRBQyBjYW4gcHJvdmlkZSB1c2VmdWwgcHJveGllcyBmb3IgdHJhbnNjcmlwdGlvbmFsIHByb2dyYW1zLCBpdCBsYWNrcyB0aGUgcmVzb2x1dGlvbiBhbmQgc3BlY2lmaWNpdHkgb2YgZGlyZWN0IFJOQSBtZWFzdXJlbWVudHMuCgojIyMgU3RlcCAzLjMgRGF0YSBpbnRlZ3JhdGlvbiAKCkludGVncmF0aW9uIG9mIEFUQUMgZGF0YXNldCBpcyB2ZXJ5IHNpbWlsYXIgdG8gUk5BLCBidXQgd2Ugd2lsbCB1c2UgdGhlIExTSSBjb21wb25lbnRzLiBIb3dldmVyLCBjdXJyZW50bHkgYFNldXJhdCB2NWAgaXMgbm90IGNvbXBhdGlibGUgd2l0aCBgQ2hyb21hdGluQXNzYXlgLCBzbyB3ZSB3aWxsIHVzZSB0aGUgb2xkKGVyKSBhcHByb2FjaC4gVGhpcyBzZWN0aW9uIGxhcmdlbHkgZm9sbG93cyB0aGUgW3NjQVRBQy1zZXEgZGF0YSBpbnRlZ3JhdGlvbiB0dXRvcmlhbF0oaHR0cHM6Ly9zdHVhcnRsYWIub3JnL3NpZ25hYy9hcnRpY2xlcy9pbnRlZ3JhdGVfYXRhYyNpbnRlZ3JhdGlvbikuCgpgYGB7cn0KRGVmYXVsdEFzc2F5KHNldXJhdCkgPC0gJ0FUQUMnCgppbnRlZ3JhdGlvbi5hbmNob3JzIDwtIEZpbmRJbnRlZ3JhdGlvbkFuY2hvcnMoCiAgb2JqZWN0Lmxpc3QgPSBTcGxpdE9iamVjdChzZXVyYXQsICJzYW1wbGUiKSwKICByZWR1Y3Rpb24gPSAicmxzaSIsICMgd2UgdXNlIFJlY2lwcm9jYWwgTFNJCiAgZGltcyA9IDI6MzAgIyBpZ25vcmUgdGhlIGZpcnN0IExTSSBjb21wb25lbnRzCikKCiMgSW50ZWdyYXRlIExTSSBlbWJlZGRpbmdzIAppbnRlZ3JhdGVkIDwtIEludGVncmF0ZUVtYmVkZGluZ3MoCiAgYW5jaG9yc2V0ID0gaW50ZWdyYXRpb24uYW5jaG9ycywKICByZWR1Y3Rpb25zID0gc2V1cmF0W1sibHNpIl1dLAogIG5ldy5yZWR1Y3Rpb24ubmFtZSA9ICJpbnRlZ3JhdGVkLmxzaSIsCiAgZGltcy50by5pbnRlZ3JhdGUgPSAxOjMwCikgCgojIEFkZCB0aGUgZGltZW5zaW9uIHJlZHVjdGlvbiB0byB0aGUgb3JpZ2luYWwgc2V1cmF0CnNldXJhdFtbJ2ludGVncmF0ZWQubHNpLmF0YWMnXV0gPC0gQ3JlYXRlRGltUmVkdWNPYmplY3QoCiAgRW1iZWRkaW5ncyhpbnRlZ3JhdGVkLCAiaW50ZWdyYXRlZC5sc2kiKVtjb2xuYW1lcyhzZXVyYXQpLF0sIAogIGtleT0iSU5URUdSQVRFRExTSUFUQUNfIiwgYXNzYXk9IkFUQUMiCikKCnNldXJhdCA8LSBSdW5VTUFQKHNldXJhdCwKICAgICAgICAgICAgICAgICAgcmVkdWN0aW9uID0gImludGVncmF0ZWQubHNpLmF0YWMiLAogICAgICAgICAgICAgICAgICBkaW1zID0gMjozMCwKICAgICAgICAgICAgICAgICAgcmVkdWN0aW9uLm5hbWUgPSAidW1hcF9hdGFjIiwKICAgICAgICAgICAgICAgICAgcmVkdWN0aW9uLmtleSA9ICJVTUFQSU5URUdSQVRFRExTSUFUQUNfIikKYGBgCgpGaW5hbGx5LCB3ZSBjYW4gdmlzdWFsaXplIG91ciBjZWxscyBpbiB0aGUgaW50ZWdyYXRlZCBBVEFDIHNwYWNlOiAKCmBgYHtyfQpEZWZhdWx0QXNzYXkoc2V1cmF0KSA8LSAnUk5BJwpwMSA8LSBEaW1QbG90KHNldXJhdCwKICAgICAgICAgICAgICBncm91cC5ieSA9ICJzYW1wbGUiLAogICAgICAgICAgICAgIHJlZHVjdGlvbiA9ICJ1bWFwX2F0YWMiKSAmIE5vQXhlcygpCnAyIDwtIEZlYXR1cmVQbG90KHNldXJhdCwKICAgICAgICAgICAgICAgICAgbWFya2VyX2dlbmVzLAogICAgICAgICAgICAgICAgICByZWR1Y3Rpb24gPSAidW1hcF9hdGFjIikgJiBOb0F4ZXMoKSAmIE5vTGVnZW5kKCkKcDEgfCBwMgpgYGAKCioqT3B0aW9uYWwqKjogU2F2ZSB0aGUgb2JqZWN0LgoKYGBge3IsIGV2YWw9RkFMU0V9CnNhdmVSRFMoc2V1cmF0LCAnb3V0L3VuaW1vZGFsX3NldXJhdC5SZHMnKQpgYGAKCiMgUGFydCA0IC0gTXVsdGltb2RhbCBkYXRhIGludGVncmF0aW9uCgojIyBTdGVwIDE6IFZpc3VhbGl6YXRpb24gaW4gVW5pbW9kYWwgRW1iZWRkaW5ncwoKYGBge3IsIGV2YWw9RkFMU0V9CiMgaWYgeW91IHN0YXJ0IGZyb20gc2VwYXJhdGVseSBpbnRlZ3JhdGVkIGRhdGEKc2V1cmF0IDwtIHJlYWRSRFMoJ291dC91bmltb2RhbF9zZXVyYXQuUmRzJykKYGBgCgpSTkEgYW5kIEFUQUMgYXNzYXlzIGNhcHR1cmUgZGlmZmVyZW50IGFzcGVjdHMgb2YgY2VsbCBpZGVudGl0eSBhbmQgcmVndWxhdGlvbi4gQXMgZGlzY3Vzc2VkIGVhcmxpZXIsIFJOQSB0eXBpY2FsbHkgcmVtYWlucyB0aGUgZG9taW5hbnQgc291cmNlIG9mIGluZm9ybWF0aW9uIGZvciBkZWZpbmluZyBjZWxsIGlkZW50aXR5IGFuZCBzdGF0ZS4gV2UgY2FuIHZpc3VhbGl6ZSB0aGlzIGJ5IGNvbXBhcmluZyB0aGUgUk5BIGFuZCBBVEFDIGVtYmVkZGluZ3Mgc2lkZSBieSBzaWRlLgoKYGBge3J9CkRlZmF1bHRBc3NheShzZXVyYXQpIDwtICJSTkEiClJlZHVjZSgifCIsIGxhcHBseShjKCJ1bWFwX3JuYSIsCiAgICAgICAgICAgICAgICAgICAgICJ1bWFwX2F0YWMiKSwgZnVuY3Rpb24oZHIpewogIHAxIDwtIERpbVBsb3Qoc2V1cmF0LAogICAgICAgICAgICAgICAgZ3JvdXAuYnkgPSAiUk5BX3Nubl9yZXMuMC43IiwKICAgICAgICAgICAgICAgIHJlZHVjdGlvbiA9IGRyKSAmIE5vTGVnZW5kKCkKICBwMiA8LSBGZWF0dXJlUGxvdChzZXVyYXQsCiAgICAgICAgICAgICAgICAgICAgbWFya2VyX2dlbmVzLAogICAgICAgICAgICAgICAgICAgIHJlZHVjdGlvbiA9IGRyKSAmIE5vQXhlcygpICYgTm9MZWdlbmQoKQogIHAxIC8gcDIKfSkpCmBgYAoKVGhpcyBzaWRlLWJ5LXNpZGUgdmlzdWFsaXphdGlvbiBoZWxwcyBpbGx1c3RyYXRlIGhvdyBlYWNoIG1vZGFsaXR5IGNhcHR1cmVzIHN0cnVjdHVyZSBpbiB0aGUgZGF0YS4gVGhlIFJOQS1iYXNlZCBlbWJlZGRpbmcgb2Z0ZW4gc2hvd3MgY2xlYXJlciBzZXBhcmF0aW9uIG9mIGNlbGwgdHlwZXMsIHdoaWxlIHRoZSBBVEFDIGVtYmVkZGluZyBtYXkgY2FwdHVyZSByZWd1bGF0b3J5IHZhcmlhdGlvbiBub3QgdmlzaWJsZSBpbiBSTkEgYWxvbmUuCgoKIyMgU3RlcCAyIC0gV2VpZ2h0ZWQgTmVhcmVzdCBOZWlnaGJvciBBbmFseXNpcwoKU28gZmFyLCB3ZSBoYXZlIHRyZWF0ZWQgdGhlIHR3byBtb2RhbGl0aWVzIHNlcGFyYXRlbHksIGdpdmVuIHRoZWlyIGRpc3RpbmN0IGRhdGEgY2hhcmFjdGVyaXN0aWNzLiAgQSBjb21tb24gc3RyYXRlZ3kgZm9yIGludGVncmF0aW5nIEFUQUMtc2VxIHdpdGggUk5BLXNlcSBkYXRhIGlzIHRvIGluZmVyIGdlbmUgYWN0aXZpdHkgc2NvcmVzIChlLmcuLCBHZW5lLkFjdGl2aXR5KSBmcm9tIGNocm9tYXRpbiBhY2Nlc3NpYmlsaXR5IHByb2ZpbGVzLgoKSG93ZXZlciwgc2luY2Ugd2UgYXJlIHdvcmtpbmcgd2l0aCBtdWx0aW9tZSBkYXRh4oCUd2hlcmUgYm90aCBBVEFDIGFuZCBSTkEgYXJlIG1lYXN1cmVkIGZyb20gdGhlIHNhbWUgY2VsbOKAlHdlIGNhbiBnbyBhIHN0ZXAgZnVydGhlci4gV2VpZ2h0ZWQgTmVhcmVzdCBOZWlnaGJvciAoV05OKSBhbmFseXNpcyBwcm92aWRlcyBhbiB1bnN1cGVydmlzZWQgZnJhbWV3b3JrIHRoYXQgbGVhcm5zIHRoZSByZWxhdGl2ZSBjb250cmlidXRpb24gb2YgZWFjaCBtb2RhbGl0eSBwZXIgY2VsbC4gVGhpcyBhbGxvd3MgdXMgdG8gcGVyZm9ybSBhIG1vcmUgaW50ZWdyYXRlZCBhbmFseXNpcyB0aGF0IGJhbGFuY2VzIGJvdGggdHJhbnNjcmlwdG9taWMgYW5kIGNocm9tYXRpbiBpbmZvcm1hdGlvbi4KClRoaXMgcGFydCBtYWlubHkgZm9sbG93cyB0aGUgW1dlaWdodGVkIE5lYXJlc3QgTmVpZ2hib3IgQW5hbHlzaXNdKGh0dHBzOi8vc2F0aWphbGFiLm9yZy9zZXVyYXQvYXJ0aWNsZXMvd2VpZ2h0ZWRfbmVhcmVzdF9uZWlnaGJvcl9hbmFseXNpcyN3bm4tYW5hbHlzaXMtb2YtMTB4LW11bHRpb21lLXJuYS1hdGFjKSB0dXRvcmlhbCBmcm9tIGBTZXVyYXRgLgoKYGBge3J9CkRlZmF1bHRBc3NheShzZXVyYXQpIDwtICdSTkEnCnNldXJhdCA8LSBGaW5kTXVsdGlNb2RhbE5laWdoYm9ycyhzZXVyYXQsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVkdWN0aW9uLmxpc3QgPSBsaXN0KCJpbnRlZ3JhdGVkLmNjYSIsICJpbnRlZ3JhdGVkLmxzaS5hdGFjIiksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGltcy5saXN0ID0gbGlzdCgxOjUwLCAyOjMwKSkKc2V1cmF0IDwtIFJ1blVNQVAoc2V1cmF0LCBubi5uYW1lID0gIndlaWdodGVkLm5uIiwgCiAgICAgICAgICAgICAgICAgIHJlZHVjdGlvbi5uYW1lID0gIndubi51bWFwIiwgcmVkdWN0aW9uLmtleSA9ICJ3bm5VTUFQXyIpCgojIEhlcmUgd2UganVzdCBjaG9vc2Ugb25lIHJlc29sdXRpb24sIGJ1dCBmZWVsIGZyZWUgdG8gY2hhbmdlIHRoaXMgcGFyYW1ldGVyCnNldXJhdCA8LSBGaW5kQ2x1c3RlcnMoc2V1cmF0LCBncmFwaC5uYW1lID0gIndzbm4iLCByZXNvbHV0aW9uID0gMC43LCB2ZXJib3NlID0gRkFMU0UpCmBgYAoKYGBge3J9CnAxIDwtIERpbVBsb3Qoc2V1cmF0LGdyb3VwLmJ5ID0gInNhbXBsZSIscmVkdWN0aW9uID0gIndubi51bWFwIikgCnAyIDwtIERpbVBsb3Qoc2V1cmF0LGdyb3VwLmJ5ID0gIndzbm5fcmVzLjAuNyIsbGFiZWw9VCwgcmVkdWN0aW9uID0gIndubi51bWFwIikgJiBOb0xlZ2VuZCgpCnAzIDwtIEZlYXR1cmVQbG90KHNldXJhdCwKICAgICAgICAgICAgICAgICAgbWFya2VyX2dlbmVzLAogICAgICAgICAgICAgICAgICByZWR1Y3Rpb24gPSAid25uLnVtYXAiKSAmIE5vQXhlcygpICYgTm9MZWdlbmQoKQpwMSAvIHAyIHwgcDMKYGBgCgpUaGlzIHZpc3VhbGl6YXRpb24gaGlnaGxpZ2h0cyBob3cgdGhlIFdOTiBhcHByb2FjaCBicmluZ3MgdG9nZXRoZXIgY29tcGxlbWVudGFyeSBzaWduYWxzIGZyb20gUk5BIGFuZCBBVEFDIHRvIHJlZmluZSBjZWxsIGNsdXN0ZXJpbmcgYW5kIGludGVycHJldGF0aW9uLgoKIyMgU3RlcCAzIC0gRGlkIHVzaW5nIGJvdGggUk5BIGFuZCBBVEFDIGltcHJvdmUgbXkgY2x1c3RlcmluZyBjb21wYXJlZCB0byB1c2luZyBqdXN0IFJOQT8KClRoaXMgcGFydCB3ZSB0cnkgdG8gYW5zd2VyIHRoaXMgcGhpbG9zb3BoaWNhbCBxdWVzdGlvbjogV2h5IGRvIHdlIHVzZSBtdWx0aW9tZT8gSXMgaXQganVzdCBiZWNhdXNlIHdlIGNhbiAoYWZmb3JkIHRvKSBkbyBpdD8KClRvIGJlZ2luIGFuc3dlcmluZyB0aGlzLCBsZXTigJlzIGNvbXBhcmUgY2x1c3RlcmluZyByZXN1bHRzOgoKYGBge3J9CiMgUk5BLW9ubHkgY2x1c3RlcmluZyAKcDEgPC0gRGltUGxvdChzZXVyYXQsIGdyb3VwLmJ5ID0gIlJOQV9zbm5fcmVzLjAuNyIsIHJlZHVjdGlvbiA9ICJ1bWFwX3JuYSIsIGxhYmVsID0gVCkgKyAKICBnZ3RpdGxlKCJSTkEtb25seSBDbHVzdGVyaW5nIikKCiMgV05OIGNsdXN0ZXJpbmcgCnAyIDwtIERpbVBsb3Qoc2V1cmF0LCBncm91cC5ieSA9ICJ3c25uX3Jlcy4wLjciLCByZWR1Y3Rpb24gPSAid25uLnVtYXAiLCBsYWJlbCA9IFQpICsgCiAgZ2d0aXRsZSgiV05OIENsdXN0ZXJpbmciKQoKcDEgJiBOb0xlZ2VuZCgpfCBwMiAmIE5vTGVnZW5kKCkgIyBDb21iaW5lIHdpdGggcGF0Y2h3b3JrCmBgYAoKQXQgZmlyc3QgZ2xhbmNlLCB0aGUgdHdvIGNsdXN0ZXJpbmcgcmVzdWx0cyBsb29rIHF1aXRlIHNpbWlsYXIuIFRoYXTigJlzIG5vdCBzdXJwcmlzaW5n4oCUUk5BIG9mdGVuIGNhcnJpZXMgdGhlIHN0cm9uZ2VzdCBzaWduYWwgZm9yIGNlbGwgaWRlbnRpdHksIGVzcGVjaWFsbHkgZm9yIGJyb2FkIGNlbGwgdHlwZXMuCgpIb3dldmVyLCB3aGVuIHVzaW5nIHRoZSBzYW1lIGNsdXN0ZXJpbmcgcmVzb2x1dGlvbiwgYWRkaW5nIHRoZSBBVEFDIG1vZGFsaXR5IHZpYSBXTk4gY2FuIHJlZmluZSBsb2NhbCBzdHJ1Y3R1cmVzIGFuZCBoZWxwIHNlcGFyYXRlIHN1YnBvcHVsYXRpb25zIHRoYXQgUk5BIGFsb25lIG1pZ2h0IG1pc3MuCgpMZXTigJlzIHF1YW50aWZ5IHRoZSBzaW1pbGFyaXR5IGJldHdlZW4gUk5BLW9ubHkgYW5kIFdOTiBjbHVzdGVycyB1c2luZyBhIGNvbnRpbmdlbmN5IHRhYmxlOgoKYGBge3J9CnRhYmxlKFJOQT1zZXVyYXQkUk5BX3Nubl9yZXMuMC43LCBXTk49c2V1cmF0JHdzbm5fcmVzLjAuNykgICU+JQogIGhlYXRtYXAoeGxhYj0nV05OIGNsdXN0ZXJpbmcnLCB5bGFiPSdSTkEtb25seSBDbHVzdGVyaW5nJywgUm93diA9IE5BLCBDb2x2ID0gTkEpCmBgYAoKSW4gc29tZSBjYXNlcywgV05OIG1pZ2h0IHVuY292ZXIgc3VidGxlIHRyYW5zaXRpb25zIG9yIHJlZ3VsYXRvcnkgc2hpZnRzIHRoYXQgYXJlIGludmlzaWJsZSBpbiB0cmFuc2NyaXB0b21pYyBzcGFjZSBhbG9uZS4KCiMjIFN0ZXAgNCAtIENlbGwgVHlwZSBBbm5vdGF0aW9uCgpJbiB0aGlzIHN0ZXAsIHdlIHVzZSB0aGUgV05OIGNsdXN0ZXJpbmcgcmVzdWx0cyB0byBhbm5vdGF0ZSBjZWxsIHR5cGVzIGJhc2VkIG9uIG1hcmtlciBnZW5lIGV4cHJlc3Npb24uCgpgYGB7cn0KRGVmYXVsdEFzc2F5KHNldXJhdCkgPC0gIlJOQSIKREVfd25uIDwtIHdpbGNveGF1YyhzZXVyYXQsICJ3c25uX3Jlcy4wLjciLCBzZXVyYXRfYXNzYXkgPSAiUk5BIikKdG9wX21hcmtlcnNfd25uIDwtIERFX3dubiAlPiUKICBmaWx0ZXIoYWJzKGxvZ0ZDKSA+IGxvZygxLjIpICYKICAgICAgICAgcGFkaiA8IDAuMDEgJgogICAgICAgICBhdWMgPiAwLjY1ICYKICAgICAgICAgcGN0X2luIC0gcGN0X291dCA+IDMwICYKICAgICAgICAgcGN0X291dCA8IDIwKSAlPiUKICBncm91cF9ieShncm91cCkgJT4lCiAgdG9wX24oMTAsIHd0ID0gYXVjKQp0b3BfbWFya2Vyc193bm4KYGBgCgpGcm9tIGhlcmUgd2UgY2FuIGFscmVhZHkgaWRlbnRpZnkgc29tZSBtYXJrZXJzIG9mIHZlcnkgZWFybHkgZGV2ZWxvcG1lbnQgc3RhZ2VzIChpLmUgUE9VNUYxLFBPVTNGMSBhbmQgUkZYNCkuCgoqKlJlZmVyZW5jZSBNYXJrZXJzIGZyb20gQnJhaW4gT3JnYW5vaWQgRGF0YSoqCgpXZSBwcm92aWRlIGEgY3VyYXRlZCBZQU1MIGZpbGUgZnJvbSBbYSBwdWJsaXNoZWQgYnJhaW4gb3JnYW5vaWQgc3R1ZHldKGh0dHBzOi8vd3d3Lm5hdHVyZS5jb20vYXJ0aWNsZXMvczQxNTg2LTAyNC0wODE3Mi04KSwgd2hpY2ggaW5jbHVkZXMgdXNlZnVsIG1hcmtlciBnZW5lcyBmb3IgYW5ub3RhdGlvbjoKCmBgYHtyfQp5YW1sX2RhdGEgPC0gcmVhZF95YW1sKCdkYXRhL21hcmtlcl9nZW5lcy55YW1sJykKZGYgPC0gYXMuZGF0YS5mcmFtZShkby5jYWxsKHJiaW5kLCBsYXBwbHkoeWFtbF9kYXRhLCBhcy5saXN0KSkpCkRvdFBsb3Qoc2V1cmF0LCBkZiRtYXJrZXJfZ2VuZXMsIGdyb3VwLmJ5ID0gJ3dzbm5fcmVzLjAuNycpICsgUm90YXRlZEF4aXMoKQpgYGAKClRvIGV4cGxvcmUgbW9yZSBzcGVjaWZpYyBzdWJ0eXBlcyAoZS5nLiwgbmV1cmFsIHByb2dlbml0b3IgY2VsbCAoTlBDKSBzdWJ0eXBlcyk6CgpgYGB7cn0KbnBjX2xpc3QgPC0gYXMuZGF0YS5mcmFtZShkby5jYWxsKHJiaW5kLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhcHBseSh5YW1sX2RhdGEkbmV1cmFsX3Byb2dlbml0b3JfY2VsbCRzdWJ0eXBlcywgYXMubGlzdCkpKQphbGxfZ2VuZXMgPC0gdW5saXN0KG5wY19saXN0JG1hcmtlcl9nZW5lcykKZHVwcyA8LSB1bmlxdWUoYWxsX2dlbmVzW2R1cGxpY2F0ZWQoYWxsX2dlbmVzKV0pCm5wY19tYXJrZXJzIDwtIGxhcHBseShucGNfbGlzdCRtYXJrZXJfZ2VuZXMsIGZ1bmN0aW9uKGdlbmVzKSB7CiAgc2V0ZGlmZihnZW5lcywgZHVwcykKfSkKRG90UGxvdChzZXVyYXQsIG5wY19tYXJrZXJzLCBncm91cC5ieSA9ICd3c25uX3Jlcy4wLjcnKSArIFJvdGF0ZWRBeGlzKCkKYGBgCgoqKkFsdGVybmF0aXZlOiBVc2UgQXZlcmFnZSBDbHVzdGVyIEV4cHJlc3Npb24qKgoKSW4gY2FzZSB5b3UgdGhpbmsgYERvdFBsb3RgIGlzIHZlcnkgZmFicmljYXRlZCwgeW91IGNhbiBjb21wdXRlIGF2ZXJhZ2UgZXhwcmVzc2lvbiBhY3Jvc3MgY2x1c3RlcnMgYW5kIHZpc3VhbGl6ZSBrbm93biBtYXJrZXJzIGluIGEgaGVhdG1hcDoKCmBgYHtyLCBmaWcuaGVpZ2h0PTEwLCBmaWcud2lkdGg9OH0KYXZnIDwtIEFnZ3JlZ2F0ZUV4cHJlc3Npb24oc2V1cmF0LCBhc3NheXMgPSAnUk5BJywgZ3JvdXAuYnk9J3dzbm5fcmVzLjAuNycsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybi5zZXVyYXQ9VFJVRSkgCmF2ZyA8LSBOb3JtYWxpemVEYXRhKGF2ZykKYXZnIDwtIEdldEFzc2F5RGF0YShvYmplY3QgPSBhdmcsIGFzc2F5ID0gIlJOQSIpIAoKbWFya2VyX2RmIDwtIHJlYWQudGFibGUoJ2RhdGEvbWFya2Vycy50eHQnLCBzZXA9J1x0JykKCnBoZWF0bWFwKGF2Z1t1bmlxdWUocm93bmFtZXMobWFya2VyX2RmKSksIF0sIAogICAgICAgICBjb2xvciA9IGNvbG9yUmFtcFBhbGV0dGUoYygibmF2eSIsIndoaXRlIiwgInJlZCIpKSgyMCksCiAgICAgICAgIGJyZWFrcyA9IHNlcSgwLDIsMC4xKSwKICAgICAgICAgc2NhbGUgPSAiY29sdW1uIiwgIyBzY2FsaW5nIGNhbiBjaGFuZ2UgdGhlIHZpc3VhbCBlZmZlY3RzIAogICAgICAgICBjbHVzdGVyX2NvbHMgPSBGQUxTRSwgY2x1c3Rlcl9yb3dzID0gRkFMU0UsCiAgICAgICAgIGFubm90YXRpb25fcm93ID0gbWFya2VyX2RmICU+JSBzZWxlY3QobGV2ZWwxLCBsZXZlbDIsIGxldmVsMykpCmBgYAoKKipBc3NpZ25pbmcgQ2VsbCBUeXBlIEFubm90YXRpb25zKioKClVzaW5nIGEgY29tYmluYXRpb24gb2Yga25vd24gbWFya2VycyBhbmQgY2x1c3RlcmluZyBzdHJ1Y3R1cmUsIHdlIGNhbiBub3cgYXNzaWduIGJyb2FkIGFubm90YXRpb25zIHRvIFdOTiBjbHVzdGVyczoKCmBgYHtyfQptZXRhIDwtIHNldXJhdEBtZXRhLmRhdGEgJT4lCiAgbXV0YXRlKHN0YWdlPWNhc2Vfd2hlbigKICAgIHdzbm5fcmVzLjAuNyAlaW4lIGMoMCwgMTQsIDMsIDkpIH4gJ3RlbGVuY2VwaGFsb24nLAogICAgd3Nubl9yZXMuMC43ICVpbiUgYygxLCAyLCA1LCA2LCA4LCAxMykgfiAnZWFybHknLAogICAgd3Nubl9yZXMuMC43ICVpbiUgYyg0LCA3LCAxMSwgMTUsIDE5KSB+ICdub250ZWxlbmNlcGhhbG9uJywKICAgIHdzbm5fcmVzLjAuNyAlaW4lIGMoMTAsIDEyLCAxNywgMTgpIH4gJ290aGVyJywKICAgIHdzbm5fcmVzLjAuNyAlaW4lIGMoMTYpIH4gJ25ldXJvbicKICApKSAlPiUKICBtdXRhdGUoY2VsbHR5cGU9aW50ZXJhY3Rpb24oc3RhZ2UsIHdzbm5fcmVzLjAuNywgZHJvcCA9IFRSVUUpKQoKc2V1cmF0IDwtIEFkZE1ldGFEYXRhKHNldXJhdCwgbWV0YSkKcDEgPC0gRGltUGxvdChzZXVyYXQsCiAgICAgICAgICAgICAgZ3JvdXAuYnkgPSAic3RhZ2UiLAogICAgICAgICAgICAgIHJlZHVjdGlvbiA9ICJ3bm4udW1hcCIsIGxhYmVsID0gVCkgJiBOb0F4ZXMoKSAmIE5vTGVnZW5kKCkKcDIgPC0gRmVhdHVyZVBsb3Qoc2V1cmF0LAogICAgICAgICAgICAgICAgICBtYXJrZXJfZ2VuZXMsCiAgICAgICAgICAgICAgICAgIHJlZHVjdGlvbiA9ICJ3bm4udW1hcCIpICYgTm9BeGVzKCkgJiBOb0xlZ2VuZCgpCnAxIHwgcDIKYGBgCgpXZSBjYW4gc2VlIHRoYXQgc29tZSBjZWxscyBhcHBlYXIgYW1iaWd1b3VzIGFuZCBhcmUgYmxlbmRlZCBpbnRvIHRoZSBlYXJseSBkZXZlbG9wbWVudGFsIHN0YWdlLiBJZiBuZWVkZWQsIHdlIGNvdWxkIGZ1cnRoZXIgcmVmaW5lIHRoaXMgYW5ub3RhdGlvbiBieSBydW5uaW5nIGBGaW5kU3ViQ2x1c3RlcigpYCB0byBzcGxpdCBzcGVjaWZpYyBjbHVzdGVycy4gQnV0IGZvciBub3csIHdl4oCZbGwga2VlcCBpdCBzaW1wbGUgYW5kIHByb2NlZWQgd2l0aCB0aGlzIGxldmVsIG9mIHJlc29sdXRpb24uCgoqKk9wdGlvbmFsKio6IFNhdmUgdGhlIG9iamVjdDoKCmBgYHtyLCBldmFsPUZBTFNFfQpzYXZlUkRTKHNldXJhdCwgJ291dC9iaW1vZGFsX3NldXJhdC5SZHMnKQpgYGAKCiMgUGFydCA1IC0gRG93bnN0cmVhbSBBbmFseXNpcyAKCiMjIFN0ZXAgMSAtIERpZmZlcmVudGlhbGx5IEV4cHJlc3NlZCBHZW5lcwoKYGBge3IsIGV2YWw9RkFMU0V9CiMgSWYgeW91IHN0YXJ0IGZyb20gcGFydCA1IGRpcmVjdGx5IApzZXVyYXQgPC0gcmVhZFJEUygnb3V0L2JpbW9kYWxfc2V1cmF0LlJkcycpCmBgYAoKSW4gdGhlIGNvbnRleHQgb2YgYSBDUklTUFIga25vY2tvdXQgKEtPKSBleHBlcmltZW50LCBvdXIgbWFpbiBnb2FsIGlzIHRvIHVuZGVyc3RhbmQgaG93IGdlbmUgZXhwcmVzc2lvbiBjaGFuZ2VzIGluIEtPIHZlcnN1cyBjb250cm9sIChXVCkgY2VsbHMuIEZvciBzaW1wbGljaXR5LCB3ZeKAmWxsIGZvY3VzIG9uIHRlbGVuY2VwaGFsb24gcHJvZ2VuaXRvcnMuCgpTdGFydCBieSBjaGVja2luZyBob3cgdGhlIHNhbXBsZXMgYXJlIGRpc3RyaWJ1dGVkIGFjcm9zcyBjZWxsIHN0YWdlczoKCmBgYHtyfQojIEhhdmUgYSBsb29rCnRhYmxlKHNldXJhdCRzYW1wbGUsIHNldXJhdCRzdGFnZSkKYGBgCgpXZeKAmWxsIHN1YnNldCBvbmx5IHRoZSB0ZWxlbmNlcGhhbG9uIGNlbGxzOgoKYGBge3J9CnNldXJhdF9zdWIgPC0gc3Vic2V0KHNldXJhdCwgc3RhZ2UgPT0gJ3RlbGVuY2VwaGFsb24nKQpgYGAKClRvIGtlZXAgdGhpbmdzIHNpbXBsZSwgd2UgY29tYmluZSBLTzEgYW5kIEtPMiBpbnRvIGEgc2luZ2xlIGdyb3VwIGFuZCBjb21wYXJlIHRoZW0gYWdhaW5zdCBXVDoKCmBgYHtyfQpzZXVyYXRfc3ViJGdyb3VwIDwtIGlmZWxzZShzZXVyYXRfc3ViJHNhbXBsZSAlaW4lIGMoIktPMSIsICJLTzIiKSwgIktPIiwgIldUIikKcmVzX2tvX3ZzX3d0IDwtIHdpbGNveGF1YyhzZXVyYXRfc3ViLCAnZ3JvdXAnLCBzZXVyYXRfYXNzYXkgPSAnUk5BJykKcmVzX2tvX3ZzX3d0X29ubHkgPC0gcmVzX2tvX3ZzX3d0W3Jlc19rb192c193dCRncm91cCA9PSAiS08iLCBdCnJlc19rb192c193dF9vbmx5ICU+JQogIGZpbHRlcihhYnMobG9nRkMpID4gbG9nKDEuMikgJgogICAgICAgICBwYWRqIDwgMC4wNSkgJT4lCiAgZ3JvdXBfYnkoZ3JvdXApICU+JQogIHRvcF9uKDEwLCB3dCA9IGF1YykKYGBgCgoqKk5PVEUqKjogSWYgeW91IHdhbnQgdG8gY29tcGFyZSBLTzEgdnMgV1QgYW5kIEtPMiB2cyBXVCBpbmRpdmlkdWFsbHksIHN1YnNldCB0aGUgZGF0YSBiZWZvcmUgcnVubmluZyBgd2lsY294YXVjKClgIHNpbmNlIGl0IGNvbXBhcmVzIGVhY2ggZ3JvdXAgdG8gYWxsIG90aGVycy4gQWx0ZXJuYXRpdmVseSwgeW91IGNhbiB1c2UgYEZpbmRNYXJrZXJzKClgIHdpdGggYGlkZW50LjFgIGFuZCBgaWRlbnQuMmAuCgoqKlBzZXVkby1idWxrIERFIEFuYWx5c2lzIHdpdGggREVTZXEyKioKClRoZSBiZXN0IHByYWN0aWNlcyBmb3IgREVHIHRlc3RpbmcgaW4gc2luZ2xlLWNlbGwgZGF0YSBhcmUgc3RpbGwgZXZvbHZpbmcuIE9uZSBzdHJhdGVneSB0byByZWR1Y2UgZmFsc2UgcG9zaXRpdmVzIGlzIHRvIHBlcmZvcm0gcHNldWRvLWJ1bGsgREUgYnkgYWdncmVnYXRpbmcgY291bnRzIGF0IHRoZSBzYW1wbGUgbGV2ZWwuCgpgYGB7cn0KcHNldWRvX2J1bGsgPC0gQWdncmVnYXRlRXhwcmVzc2lvbihzZXVyYXRfc3ViLCBhc3NheXMgPSAnUk5BJywgZ3JvdXAuYnk9J3NhbXBsZScpJFJOQQpzYW1wbGVzIDwtIGNvbG5hbWVzKHBzZXVkb19idWxrKQpjb25kaXRpb24gPC0gaWZlbHNlKHNhbXBsZXMgJWluJSBjKCJLTzEiLCAiS08yIiksICJLTyIsICJXVCIpCgpzYW1wbGVfaW5mbyA8LSBkYXRhLmZyYW1lKAogIHNhbXBsZSA9IHNhbXBsZXMsCiAgY29uZGl0aW9uID0gY29uZGl0aW9uCikKCmRkcyA8LSBERVNlcURhdGFTZXRGcm9tTWF0cml4KGNvdW50RGF0YSA9IHBzZXVkb19idWxrLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xEYXRhID0gc2FtcGxlX2luZm8sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRlc2lnbiA9IH4gY29uZGl0aW9uKQoKZGRzIDwtIERFU2VxKGRkcykKCiMgZXh0cmFjdCB0aGUgcmVzdWx0cyBvZiBLT3MgdnMgV1QKcmVzIDwtIHJlc3VsdHMoZGRzLCBjb250cmFzdCA9IGMoImNvbmRpdGlvbiIsICJLTyIsICJXVCIpKQpyZXNfZGYgPC0gYXMuZGF0YS5mcmFtZShyZXMpCnJlc19kZiA8LSByZXNfZGZbb3JkZXIocmVzX2RmJHBhZGopLCBdCmhlYWQocmVzX2RmLCBuPTEwKQpgYGAKCioqVm9sY2FubyBQbG90czogU2luZ2xlLWNlbGwgdnMgUHNldWRvLWJ1bGsgREUqKgoKYGBge3J9CiMjIFBsb3Qgc2luZ2xlIGNlbGwgCnJlc19rb192c193dF9vbmx5JGxvZzJGQyA9IHJlc19rb192c193dF9vbmx5JGxvZ0ZDL2xvZygyKQpyZXNfa29fdnNfd3Rfb25seSRzaWduaWZpY2FuY2UgPC0gaWZlbHNlKHJlc19rb192c193dF9vbmx5JHBhZGogPCAwLjA1ICYgYWJzKHJlc19rb192c193dF9vbmx5JGxvZzJGQykgPiAwLjIsICJTaWduaWZpY2FudCIsICJOb3Qgc2lnbmlmaWNhbnQiKQoKIyBTZWxlY3QgdG9wIDEwIG1vc3Qgc2lnbmlmaWNhbnQgZ2VuZXMKdG9wX2dlbmVzIDwtIGhlYWQocmVzX2tvX3ZzX3d0X29ubHlbb3JkZXIocmVzX2tvX3ZzX3d0X29ubHkkcGFkaiksICJmZWF0dXJlIl0sIDEwKQp0b3BfZ2VuZXMgPC0gYyh0b3BfZ2VuZXMsICdFTVgyJywgJ0dMSTMnLCdQQVg2JywnU09YNCcsJ1NPWDExJykKcmVzX2tvX3ZzX3d0X29ubHkkbGFiZWwgPC0gaWZlbHNlKHJlc19rb192c193dF9vbmx5JGZlYXR1cmUgJWluJSB0b3BfZ2VuZXMsIHJlc19rb192c193dF9vbmx5JGZlYXR1cmUsIE5BKQoKcDEgPC0gZ2dwbG90KHJlc19rb192c193dF9vbmx5LCBhZXMoeCA9IGxvZzJGQywgeSA9IC1sb2cxMChwYWRqKSwgY29sb3IgPSBzaWduaWZpY2FuY2UpKSArCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuOCkgKwogIGdlb21fdGV4dF9yZXBlbChhZXMobGFiZWwgPSBsYWJlbCksIG1heC5vdmVybGFwcyA9IDUwKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMoImdyZXkiLCAicmVkIikpICsKICB0aGVtZV9taW5pbWFsKCkgKwogIGxhYnModGl0bGUgPSAiVm9sY2FubyBQbG90IChTaW5nbGUtY2VsbCBERSkiLCB4ID0gImxvZzIgRm9sZCBDaGFuZ2UiLCB5ID0gIi1sb2cxMChhZGp1c3RlZCBwLXZhbHVlKSIpCgojIyBQbG90IG1pbmlidWxrCnJlc19kZiRnZW5lIDwtIHJvd25hbWVzKHJlc19kZikKcmVzX2RmJHNpZ25pZmljYW5jZSA8LSBpZmVsc2UocmVzX2RmJHBhZGogPCAwLjA1ICYgYWJzKHJlc19kZiRsb2cyRm9sZENoYW5nZSkgPiAwLjIsICJTaWduaWZpY2FudCIsICJOb3Qgc2lnbmlmaWNhbnQiKQoKdG9wX2dlbmVzIDwtIGhlYWQocmVzX2RmW29yZGVyKHJlc19kZiRwYWRqKSwgImdlbmUiXSwgMTApCnRvcF9nZW5lcyA8LSBjKHRvcF9nZW5lcywgJ0VNWDInLCAnR0xJMycsJ1BBWDYnLCdTT1g0JywnU09YMTEnKQpyZXNfZGYkbGFiZWwgPC0gaWZlbHNlKHJlc19kZiRnZW5lICVpbiUgdG9wX2dlbmVzLCByZXNfZGYkZ2VuZSwgTkEpCgpwMiA8LSBnZ3Bsb3QocmVzX2RmLCBhZXMoeCA9IGxvZzJGb2xkQ2hhbmdlLCB5ID0gLWxvZzEwKHBhZGopLCBjb2xvciA9IHNpZ25pZmljYW5jZSkpICsKICBnZW9tX3BvaW50KGFscGhhID0gMC44KSArCiAgZ2VvbV90ZXh0X3JlcGVsKGFlcyhsYWJlbCA9IGxhYmVsKSwgbWF4Lm92ZXJsYXBzID0gNTApICsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYygiZ3JleSIsICJibHVlIikpICsKICB0aGVtZV9taW5pbWFsKCkgKwogIGxhYnModGl0bGUgPSAiVm9sY2FubyBQbG90IChQc2V1ZG8tYnVsayBERSkiLCB4ID0gImxvZzIgRm9sZCBDaGFuZ2UiLCB5ID0gIi1sb2cxMChhZGp1c3RlZCBwLXZhbHVlKSIpCgpwMSAmIE5vTGVnZW5kKCl8IHAyICYgTm9MZWdlbmQoKQpgYGAKCiMjIFN0ZXAgMiAtIENocm9tYXRpbiBBY2Nlc3NpYmlsaXR5IFByb2ZpbGVzIEFyb3VuZCBNYXJrZXJzCgpTbyBmYXIsIHdl4oCZdmUgYmVlbiB0cmVhdGluZyB0aGUgQVRBQyBhc3NheSBhcyBqdXN0IGFub3RoZXIgZGF0YSBsYXllcuKAlGJ1dCB3aGF0ICphZGRpdGlvbmFsIGJpb2xvZ2ljYWwgaW5zaWdodHMqIGNhbiBpdCBwcm92aWRlPwoKQ2hyb21hdGluIGFjY2Vzc2liaWxpdHkgcmVmbGVjdHMgdGhlIG9wZW5uZXNzIG9mIGdlbm9taWMgcmVnaW9ucy4gVGhlIG1vcmUg4oCcb3BlbuKAnSBhIHJlZ2lvbiBpcywgdGhlIG1vcmUgbGlrZWx5IGl0IGlzIGFjY2Vzc2libGUgdG8gdHJhbnNjcmlwdGlvbiBmYWN0b3JzIGFuZCBvdGhlciByZWd1bGF0b3J5IHByb3RlaW5zLiBUaGlzIGFjY2Vzc2liaWxpdHkgb2Z0ZW4gY29ycmVsYXRlcyB3aXRoIHJlZ3VsYXRvcnkgYWN0aXZpdHkuCgpUaGUgcG93ZXIgb2YgKm11bHRpb21lIGRhdGEqIGlzIHRoYXQgd2UgY2FuIG1lYXN1cmUgYm90aCBjaHJvbWF0aW4gYWNjZXNzaWJpbGl0eSBhbmQgZ2VuZSBleHByZXNzaW9uIGluIHRoZSBzYW1lIGNlbGxzLCBlbmFibGluZyBkaXJlY3QgaW5zaWdodHMgaW50byBnZW5lIHJlZ3VsYXRpb24uCgpJbiB0aGlzIHN0ZXAsIHdl4oCZbGw6CgkxLglVc2UgYFJlZ2lvblN0YXRzKClgIHRvIGNvbXB1dGUgYmFzZSBjb21wb3NpdGlvbiBpbmZvcm1hdGlvbiBmb3IgcGVha3MuCgkyLglVc2UgYExpbmtQZWFrcygpYCB0byBhc3NvY2lhdGUgcGVha3Mgd2l0aCBuZWFyYnkgZ2VuZXMgYmFzZWQgb24gY29ycmVsYXRpb24gYmV0d2VlbiBhY2Nlc3NpYmlsaXR5IGFuZCBleHByZXNzaW9uLgoKVGhpcyBzZWN0aW9uIGlzIG1vc3RseSBpbnNwaXJlZCBmcm9tIFt0aGlzIHNlY3Rpb24gb2YgdHV0b3JpYWxdKGh0dHBzOi8vZ2l0aHViLmNvbS9xdWFkYmlvL3NjTXVsdGlvbWVfYW5hbHlzaXNfdmlnbmV0dGUvYmxvYi9tYWluL1R1dG9yaWFsLm1kI3N0ZXAtMi1jZWxsLXR5cGUtZ2VuZXBlYWstbWFya2VyLWlkZW50aWZpY2F0aW9uLWFuZC12aXN1YWxpemF0aW9uLW9mLXRoZS1jaHJvbWF0aW4tYWNjZXNzaWJpbGl0eS1wcm9maWxlcykuCgpgYGB7cn0KbGlicmFyeShCU2dlbm9tZS5Ic2FwaWVucy5VQ1NDLmhnMzgpCnNldXJhdF9zdWIgPC0gUmVnaW9uU3RhdHMoc2V1cmF0X3N1Yixhc3NheSA9ICdBVEFDJywKICAgICAgICAgICAgICAgICAgICAgIGdlbm9tZSA9IEJTZ2Vub21lLkhzYXBpZW5zLlVDU0MuaGczOCkKc2V1cmF0X3N1YiA8LSBMaW5rUGVha3Moc2V1cmF0X3N1YiwKICAgICAgICAgICAgICAgICAgICBwZWFrLmFzc2F5ID0gIkFUQUMiLAogICAgICAgICAgICAgICAgICAgIGV4cHJlc3Npb24uYXNzYXkgPSAiUk5BIiwKICAgICAgICAgICAgICAgICAgICBnZW5lcy51c2UgPSB0b3BfZ2VuZXMpCmBgYAoKWW91IGNhbiB2aXN1YWxpemUgcGVhay1nZW5lIGxpbmtzIHdpdGggQ292ZXJhZ2VQbG90KCkgYW5kIGV4cGxvcmUgc3BlY2lmaWMgcmVndWxhdG9yeSByZWxhdGlvbnNoaXBzIHVzaW5nIGJyb3dzZXItc3R5bGUgdmlld3MuCgpgYGB7cixmaWcuaGVpZ2h0PTEwLCBmaWcud2lkdGg9OH0KRGVmYXVsdEFzc2F5KHNldXJhdF9zdWIpIDwtICJBVEFDIgpwMSA8LSBDb3ZlcmFnZVBsb3Qoc2V1cmF0X3N1YiwKICAgICAgICAgICAgICAgICAgIHJlZ2lvbiA9ICJIRVM0IiwKICAgICAgICAgICAgICAgICAgIGZlYXR1cmVzID0gIkhFUzQiLAogICAgICAgICAgICAgICAgICAgZ3JvdXAuYnkgPSAic2FtcGxlIiwKICAgICAgICAgICAgICAgICAgIGV4dGVuZC51cHN0cmVhbSA9IDUwMDAsCiAgICAgICAgICAgICAgICAgICBleHRlbmQuZG93bnN0cmVhbSA9IDUwMDApCnAyIDwtIENvdmVyYWdlUGxvdChzZXVyYXRfc3ViLAogICAgICAgICAgICAgICAgICAgcmVnaW9uID0gIlBBWDIiLAogICAgICAgICAgICAgICAgICAgZmVhdHVyZXMgPSAiUEFYMiIsCiAgICAgICAgICAgICAgICAgICBncm91cC5ieSA9ICJzYW1wbGUiLAogICAgICAgICAgICAgICAgICAgZXh0ZW5kLnVwc3RyZWFtID0gNTAwMCwKICAgICAgICAgICAgICAgICAgIGV4dGVuZC5kb3duc3RyZWFtID0gNTAwMCkKcGF0Y2h3b3JrOjp3cmFwX3Bsb3RzKHAxLCBwMiwgbmNvbCA9IDEpCmBgYAoKIyMgU3RlcCAzIC0gVEYgYmluZGluZyBtb3RpZiBlbnJpY2htZW50IGFuYWx5c2lzCgpGb2xsb3dpbmcgdGhlIGlkZW50aWZpY2F0aW9uIG9mIGRpZmZlcmVudGlhbGx5IGFjY2Vzc2libGUgY2hyb21hdGluIHJlZ2lvbnMgaW4gU3RlcCAyLCB3ZSBub3cgd2FudCB0byBhc2s6CldoaWNoIHRyYW5zY3JpcHRpb24gZmFjdG9ycyAoVEZzKSBtaWdodCBiZSBkcml2aW5nIHRoZXNlIGNoYW5nZXMgaW4gY2hyb21hdGluIGFjY2Vzc2liaWxpdHk/CgpUaGlzIGlzIGNvbW1vbmx5IGRvbmUgYnkgYW5hbHl6aW5nIGVucmljaG1lbnQgb2YgVEYgYmluZGluZyBtb3RpZnMgaW4gdGhlIGFjY2Vzc2libGUgcmVnaW9ucy4KCiMjIyBTdGVwIDMuMSAtIExvYWQgTW90aWYgRGF0YWJhc2UgYW5kIEFkZCBNb3RpZnMgdG8gdGhlIFNldXJhdCBPYmplY3QKCldl4oCZbGwgdXNlIHRoZSBKQVNQQVIyMDIwIGRhdGFiYXNlIG9mIHZlcnRlYnJhdGUgbW90aWZzIGFuZCBhbm5vdGF0ZSBwZWFrcyB3aXRoIGtub3duIFRGIG1vdGlmcyB1c2luZyBBZGRNb3RpZnMoKS4KCmBgYHtyfQpsaWJyYXJ5KFRGQlNUb29scykKbGlicmFyeShKQVNQQVIyMDIwKQoKcGZtIDwtIGdldE1hdHJpeFNldCgKICB4ID0gSkFTUEFSMjAyMCwKICBvcHRzID0gbGlzdChjb2xsZWN0aW9uID0gIkNPUkUiLCB0YXhfZ3JvdXAgPSAndmVydGVicmF0ZXMnLCBhbGxfdmVyc2lvbnMgPSBGQUxTRSkKKQpkZl9wZm0gPC0gZGF0YS5mcmFtZSh0KHNhcHBseShwZm0sIGZ1bmN0aW9uKHgpCiAgYyhpZD14QElELCBuYW1lPXhAbmFtZSwgc3ltYm9sPWlmZWxzZSghaXMubnVsbCh4QHRhZ3Mkc3ltYm9sKSx4QHRhZ3Mkc3ltYm9sLE5BKSkpKSkKCnNldXJhdF9zdWIgPC0gQWRkTW90aWZzKHNldXJhdF9zdWIsIGdlbm9tZSA9IEJTZ2Vub21lLkhzYXBpZW5zLlVDU0MuaGczOCwgcGZtID0gcGZtKQpgYGAKCiMjIyBTdGVwIDMuMiAtIElkZW50aWZ5IERpZmZlcmVudGlhbGx5IEFjY2Vzc2libGUgUGVha3MgKERBUHMpIGluIEtPIHZzIFdUCgpTaW1pbGFyIHRvIGdlbmUgZXhwcmVzc2lvbiBhbmFseXNpcywgd2Ugbm93IHRlc3QgZm9yIGRpZmZlcmVudGlhbGx5IGFjY2Vzc2libGUgcGVha3MgdXNpbmcgdGhlIEFUQUMgYXNzYXkuCgpgYGB7cn0KcmVzX2tvX3ZzX3d0X2F0YWMgPC0gd2lsY294YXVjKHNldXJhdF9zdWIsICdncm91cCcsIHNldXJhdF9hc3NheSA9ICdBVEFDJykKcmVzX2tvX3ZzX3d0X2F0YWMgPC0gcmVzX2tvX3ZzX3d0X2F0YWNbcmVzX2tvX3ZzX3d0X2F0YWMkZ3JvdXAgPT0gIktPIiwgXQp0b3Bfa29fYXRhYyA8LSAKICByZXNfa29fdnNfd3RfYXRhYyAlPiUKICBmaWx0ZXIocGFkaiA8IDAuMDEgJgogICAgICAgICBhdWMgPiAwLjYpIAp0b3Bfa29fYXRhYwpgYGAKCllvdSBjYW4gdmlzdWFsaXplIGFjY2Vzc2liaWxpdHkgZGlmZmVyZW5jZXMgaW4gc3BlY2lmaWMgcmVnaW9ucyB1c2luZyBgQ292ZXJhZ2VQbG90KClgOgoKYGBge3J9CkNvdmVyYWdlUGxvdChzZXVyYXRfc3ViLAogICAgICAgICAgICAgICAgICAgcmVnaW9uID0gImNocjExLTEyMjE0OTUzLTEyMjE1ODU4IiwKICAgICAgICAgICAgICAgICAgIGdyb3VwLmJ5ID0gInNhbXBsZSIsCiAgICAgICAgICAgICAgICAgICBleHRlbmQudXBzdHJlYW0gPSA1MDAwLAogICAgICAgICAgICAgICAgICAgZXh0ZW5kLmRvd25zdHJlYW0gPSA1MDAwKQpgYGAKCiMjIyBTdGVwIDMuMyBQZXJmb3JtIE1vdGlmIEVucmljaG1lbnQgaW4gRGlmZmVyZW50aWFsbHkgQWNjZXNzaWJsZSBQZWFrcwoKQmVmb3JlIHJ1bm5pbmcgbW90aWYgZW5yaWNobWVudCwgd2UgbWF0Y2ggYmFja2dyb3VuZCBwZWFrcyBiYXNlZCBvbiBHQyBjb250ZW50IGFuZCBvdGhlciByZWdpb24gc3RhdGlzdGljcyB0byBjb250cm9sIGZvciBiaWFzZXMuCgpgYGB7cn0KYXRhY19wZWFrcyA8LSBBY2Nlc3NpYmxlUGVha3Moc2V1cmF0X3N1YikKcGVha3NfbWF0Y2hlZCA8LSBNYXRjaFJlZ2lvblN0YXRzKAogIG1ldGEuZmVhdHVyZSA9IHNldXJhdF9zdWJbWydBVEFDJ11dQG1ldGEuZmVhdHVyZXNbYXRhY19wZWFrcywgXSwgIyBHZXQgdGhlIHJlZ2lvbiBzdGF0cywgbGlrZSBHQyUgZXRjCiAgcXVlcnkuZmVhdHVyZSA9IHNldXJhdF9zdWJbWydBVEFDJ11dQG1ldGEuZmVhdHVyZXNbdG9wX2tvX2F0YWMkZmVhdHVyZSwgXSkKYGBgCgpOb3cgd2UgY2FuIHBlcmZvcm0gbW90aWYgZW5yaWNobWVudCBvbiB0aGUgZGlmZmVyZW50aWFsbHkgYWNjZXNzaWJsZSBwZWFrczoKCmBgYHtyfQptb3RpZl9lbnJpY2htZW50X2tvIDwtIEZpbmRNb3RpZnMoc2V1cmF0X3N1YiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZlYXR1cmVzID0gdG9wX2tvX2F0YWMkZmVhdHVyZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJhY2tncm91bmQgPSBwZWFrc19tYXRjaGVkKSAlPiUKICBtdXRhdGUoc3ltYm9sID0gc2V0TmFtZXMoaWZlbHNlKGlzLm5hKGRmX3BmbSRzeW1ib2wpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRmX3BmbSRuYW1lLCBkZl9wZm0kc3ltYm9sKSwgZGZfcGZtJGlkKVttb3RpZl0pICU+JQogIG11dGF0ZShwYWRqID0gcC5hZGp1c3QocHZhbHVlLCBtZXRob2Q9IkJIIikpCnRvcF9rb19tb3RpZiA8LSBtb3RpZl9lbnJpY2htZW50X2tvICU+JQogIGZpbHRlcihwdmFsdWUgPCAwLjAxICYgZm9sZC5lbnJpY2htZW50ID4gMykgIyNOT1RFOiB5b3UgY2FuIGNob29zZSB0aGUgZmlsdGVyIGNyaXRlcmlhCnRvcF9rb19tb3RpZgpgYGAKCldlIGNhbiB2aXN1YWxpemUgdGhlIG1vdGlmczogCgpgYGB7cn0KTW90aWZQbG90KHNldXJhdF9zdWIsIG1vdGlmcyA9IHRvcF9rb19tb3RpZiRtb3RpZiwgbmNvbD0yKQpgYGAKCioqV2hhdCBkb2VzIHRoaXMgdGVsbCB1cz8qKgoKVGhlIGVucmljaGVkIG1vdGlmcyBpbiBLTy1zcGVjaWZpYyBhY2Nlc3NpYmxlIHBlYWtzIG1heSBwb2ludCB0byBURnMgdGhhdCBhcmUgbW9yZSBhY3RpdmUgb3IgdXBzdHJlYW0gcmVndWxhdG9ycyBvZiB0aGUgZ2VuZSBleHByZXNzaW9uIHByb2dyYW1zIGFsdGVyZWQgYnkgdGhlIGtub2Nrb3V0LgoKIyBQYXJ0IDYg4oCTIFdoYXTigJlzIE5leHQ/IEFkZGl0aW9uYWwgRG93bnN0cmVhbSBBbmFseXNlcwoKTm93IHRoYXQgd2XigJl2ZSBjb3ZlcmVkIHRoZSBjb3JlIG11bHRpbW9kYWwgd29ya2Zsb3figJR2aXN1YWxpemF0aW9uLCBpbnRlZ3JhdGlvbiwgYW5ub3RhdGlvbiwgZGlmZmVyZW50aWFsIGFuYWx5c2lzLCBhbmQgbW90aWYgZW5yaWNobWVudC4gQmVsb3cgYXJlIHNvbWUgaWRlYXMgZm9yIGFkZGl0aW9uYWwgZG93bnN0cmVhbSBzdGVwczoKCjEuIEdlbmUgUmVndWxhdG9yeSBOZXR3b3JrIChHUk4pIEluZmVyZW5jZQoqIFVzZSB0b29scyBsaWtlIFNDRU5JQywgcHlTQ0VOSUMsIG9yIEdSTkJvb3N0IHRvIGluZmVyIHRyYW5zY3JpcHRpb24gZmFjdG9yLXRhcmdldCBnZW5lIG5ldHdvcmtzIGZyb20gZ2VuZSBleHByZXNzaW9uLgoqIENvbWJpbmUgZXhwcmVzc2lvbiArIGNocm9tYXRpbiBhY2Nlc3NpYmlsaXR5IHRvIGJ1aWxkIG11bHRpLWxheWVyZWQgR1JOcywgd2hlcmUgb25lIGxheWVyIHJlcHJlc2VudHMgY2hyb21hdGluIHN0YXRlIGFuZCBhbm90aGVyIHJlcHJlc2VudHMgZXhwcmVzc2lvbi4KCjIuIFRyYWplY3RvcnkgYW5kIFBzZXVkb3RpbWUgQW5hbHlzaXMKKiBBcHBseSB0cmFqZWN0b3J5IGluZmVyZW5jZSB0b29scyAoZS5nLiwgTW9ub2NsZSAzLCBTbGluZ3Nob3QsIHNjVmVsbywgQ2VsbFJhbmspIHRvIG1vZGVsIGRldmVsb3BtZW50YWwgcHJvZ3Jlc3Npb24uCiogSW50ZWdyYXRlIEFUQUMgYW5kIFJOQSBtb2RhbGl0aWVzIGluIHBzZXVkb3RpbWUgdG8gdW5kZXJzdGFuZCB0aGUgdGVtcG9yYWwgcmVndWxhdGlvbiBvZiBib3RoIGNocm9tYXRpbiBhbmQgZ2VuZSBleHByZXNzaW9uLgoKMy4gQ2VsbC1DZWxsIENvbW11bmljYXRpb24KKglVc2UgdG9vbHMgbGlrZSBDZWxsQ2hhdCwgTkFUTUksIG9yIE5pY2hlTmV0IHRvIGlkZW50aWZ5IGxpZ2FuZC1yZWNlcHRvciBpbnRlcmFjdGlvbnMgYmV0d2VlbiBjZWxsIHR5cGVzLgoqCUNvbWJpbmUgd2l0aCBjaHJvbWF0aW4gYWNjZXNzaWJpbGl0eSB0byB1bmRlcnN0YW5kIHdoZXRoZXIgcmVndWxhdG9yeSByZWdpb25zIG5lYXIgbGlnYW5kL3JlY2VwdG9yIGdlbmVzIGFyZSBvcGVuL2Nsb3NlZC4KCjQuIENocm9tYXRpbiBEeW5hbWljcwoqCVN0dWR5IGRpZmZlcmVudGlhbCBwZWFrIHVzYWdlIGFjcm9zcyBkZXZlbG9wbWVudGFsIHN0YWdlcywgY2x1c3RlcnMsIG9yIGNvbmRpdGlvbnMuCioJVmlzdWFsaXplIHNwZWNpZmljIGxvY2kgYW5kIHRyYWNrIGFjY2Vzc2liaWxpdHkgY2hhbmdlcyBvdmVyIHBzZXVkb3RpbWUgb3IgY2x1c3RlcnMuCgo1LiBFbmhhbmNlci1HZW5lIExpbmsgUHJlZGljdGlvbgoqCVJlZmluZSBwZWFrLXRvLWdlbmUgbGlua3Mgd2l0aCBhZGRpdGlvbmFsIHRvb2xzIGxpa2UgQ2ljZXJvLCBBcmNoUiwgb3IgQUJDIG1vZGVsIHByZWRpY3Rpb25zLgoqCVZhbGlkYXRlIHByZWRpY3RlZCBlbmhhbmNlci1nZW5lIHBhaXJzIHdpdGggbGl0ZXJhdHVyZSBvciBleHRlcm5hbCBkYXRhc2V0cyAoZS5nLiwgQ2hJUC1zZXEsIEhpLUMpLgoKNi4gRnVuY3Rpb25hbCBFbnJpY2htZW50IEFuYWx5c2lzCioJREVHcwoqCUdlbmVzIGxpbmtlZCB0byBkaWZmZXJlbnRpYWwgcGVha3MKKglURnMgaWRlbnRpZmllZCBpbiBtb3RpZiBlbnJpY2htZW50ClVzZSBwYWNrYWdlcyBsaWtlIGNsdXN0ZXJQcm9maWxlciwgZW5yaWNoUiwgb3IgZ3Byb2ZpbGVyMi4KCjcuIENvbXBhcmF0aXZlIEFuYWx5c2lzCioJQ29tcGFyZSBkaWZmZXJlbnQga25vY2tvdXRzIChLTzEgdnMgS08yKSwgdGltZSBwb2ludHMsIG9yIG9yZ2Fub2lkIHZzIGluIHZpdm8gZGF0YXNldHMuCioJUHJvamVjdCB5b3VyIGRhdGEgb250byBhIHB1YmxpYyBhdGxhcyB0byBjaGVjayBob3cgY2xvc2UgeW91ciBtb2RlbCBpcyB0byBpbiB2aXZvLgoK